aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorServo WPT Sync <32481905+servo-wpt-sync@users.noreply.github.com>2024-07-14 05:16:30 -0400
committerGitHub <noreply@github.com>2024-07-14 09:16:30 +0000
commit968474a9fda45b951de9b9b16b5a1fae16c5f2a7 (patch)
treec47b6bff4605d271dd47923c62f11d747ea0c6b9
parent3118542a9e90478cdabf1f2479851f86dd0e94d6 (diff)
downloadservo-968474a9fda45b951de9b9b16b5a1fae16c5f2a7.tar.gz
servo-968474a9fda45b951de9b9b16b5a1fae16c5f2a7.zip
Update web-platform-tests to revision b'f3dd9cba239a9655951ee62ec4dafc8fe37df2c5' (#32774)
Signed-off-by: WPT Sync Bot <ghbot+wpt-sync@servo.org>
-rw-r--r--tests/wpt/meta-legacy-layout/css/compositing/mix-blend-mode/mix-blend-mode-video.html.ini3
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-animations/display-none-prevents-starting-in-subtree.html.ini3
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-fonts/generic-family-keywords-001.html.ini9
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-fonts/parsing/font-family-valid.html.ini3
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-fonts/system-ui-ar.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-fonts/system-ui-mixed.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-fonts/system-ui-ur.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-fonts/system-ui.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-022.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-023.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-024.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-025.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-026.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-027.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-017.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-018.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-019.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-020.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-021.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-022.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-023.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-024.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-025.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-026.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-027.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-028.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-029.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-030.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-031.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-032.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-with-abspos-012.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-with-abspos-013.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-overflow/overflow-video.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-transforms/transform-input-017.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-transforms/transform-input-018.html.ini1
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-transitions/pseudo-element-transform.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-transitions/transitions-retarget.html.ini3
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-values/calc-size/animation/interpolate-size-interpolation.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-values/calc-size/interpolate-size-computed.html.ini6
-rw-r--r--tests/wpt/meta-legacy-layout/css/css-values/calc-size/interpolate-size-parsing.html.ini6
-rw-r--r--tests/wpt/meta-legacy-layout/css/cssom-view/MediaQueryListEvent.html.ini5
-rw-r--r--tests/wpt/meta-legacy-layout/css/selectors/media/media-playback-state.html.ini5
-rw-r--r--tests/wpt/meta-legacy-layout/custom-elements/CustomElementRegistry-getName.html.ini9
-rw-r--r--tests/wpt/meta-legacy-layout/dom/observable/tentative/observable-constructor.any.js.ini18
-rw-r--r--tests/wpt/meta-legacy-layout/fetch/metadata/generated/css-font-face.sub.tentative.html.ini3
-rw-r--r--tests/wpt/meta-legacy-layout/fetch/orb/tentative/known-mime-type.sub.any.js.ini89
-rw-r--r--tests/wpt/meta-legacy-layout/fetch/orb/tentative/nosniff.sub.any.js.ini42
-rw-r--r--tests/wpt/meta-legacy-layout/fetch/orb/tentative/status.sub.any.js.ini24
-rw-r--r--tests/wpt/meta-legacy-layout/fetch/range/non-matching-range-response.html.ini3
-rw-r--r--tests/wpt/meta-legacy-layout/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-cross-origin.sub.html.ini4
-rw-r--r--tests/wpt/meta-legacy-layout/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-same-origin.sub.html.ini4
-rw-r--r--tests/wpt/meta-legacy-layout/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html.ini4
-rw-r--r--tests/wpt/meta-legacy-layout/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html.ini4
-rw-r--r--tests/wpt/meta-legacy-layout/html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html.ini4
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/element/manual/imagebitmap/createImageBitmap-colorSpaceConversion.html.ini1
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/element/manual/imagebitmap/createImageBitmap-serializable.html.ini3
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.caret-position-edge-cases.tentative.html.ini3
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.caret-position-edges.tentative.html.ini30
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.caret-position.tentative.html.ini120
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html.ini12
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.getActualBoundingBox.tentative.html.ini18
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.selection-rects.tentative.html.ini72
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillText.html.ini3
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillText.worker.js.ini3
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/offscreen/reset/2d.reset.render.text.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.html.ini3
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.worker.js.ini3
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.html.ini30
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.worker.js.ini30
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.caret-position.tentative.html.ini120
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.worker.js.ini2
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html.ini12
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.worker.js.ini12
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.html.ini18
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.worker.js.ini18
-rw-r--r--tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.selection-rects.tentative.html.ini72
-rw-r--r--tests/wpt/meta-legacy-layout/html/dom/elements/global-attributes/cdata-dir_auto.html.ini12
-rw-r--r--tests/wpt/meta-legacy-layout/html/dom/elements/global-attributes/dir-assorted.window.js.ini3
-rw-r--r--tests/wpt/meta-legacy-layout/html/dom/elements/global-attributes/dir-auto-dynamic-changes.window.js.ini24
-rw-r--r--tests/wpt/meta-legacy-layout/html/dom/elements/global-attributes/dir-slots-directionality.html.ini9
-rw-r--r--tests/wpt/meta-legacy-layout/html/dom/idlharness.https.html.ini54
-rw-r--r--tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/audio_loop_base.html.ini5
-rw-r--r--tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/audio_loop_seek_to_eos.html.ini3
-rw-r--r--tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/playing-the-media-resource/loop-from-ended.tentative.html.ini4
-rw-r--r--tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/track/track-element/no-cuechange-before-play.html.ini3
-rw-r--r--tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/track/track-element/track-cue-mutable-fragment.html.ini4
-rw-r--r--tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/track/track-element/track-mode-not-changed-by-new-track.html.ini4
-rw-r--r--tests/wpt/meta-legacy-layout/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-nav-form-submit.html.ini3
-rw-r--r--tests/wpt/meta-legacy-layout/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-nav-location-assign.html.ini3
-rw-r--r--tests/wpt/meta-legacy-layout/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-3.html.ini3
-rw-r--r--tests/wpt/meta-legacy-layout/html/semantics/invokers/idlharness.tentative.html.ini36
-rw-r--r--tests/wpt/meta-legacy-layout/html/semantics/invokers/invokeelement-interface.tentative.html.ini30
-rw-r--r--tests/wpt/meta-legacy-layout/page-visibility/iframe-session-history.html.ini3
-rw-r--r--tests/wpt/meta-legacy-layout/page-visibility/iframe-unload.html.ini4
-rw-r--r--tests/wpt/meta-legacy-layout/page-visibility/onvisibilitychange.html.ini4
-rw-r--r--tests/wpt/meta-legacy-layout/page-visibility/test_child_document.html.ini9
-rw-r--r--tests/wpt/meta-legacy-layout/page-visibility/unload-bubbles.html.ini4
-rw-r--r--tests/wpt/meta-legacy-layout/page-visibility/unload.html.ini4
-rw-r--r--tests/wpt/meta-legacy-layout/resource-timing/test_resource_timing.https.html.ini3
-rw-r--r--tests/wpt/meta/MANIFEST.json2510
-rw-r--r--tests/wpt/meta/css/css-animations/display-none-prevents-starting-in-subtree.html.ini3
-rw-r--r--tests/wpt/meta/css/css-color/animation/opacity-animation-ending-correctly-002.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-022.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-023.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-024.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-025.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-026.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-027.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-017.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-018.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-019.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-020.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-021.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-022.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-023.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-024.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-025.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-026.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-027.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-028.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-029.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-030.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-031.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-032.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-with-abspos-012.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-with-abspos-013.tentative.html.ini2
-rw-r--r--tests/wpt/meta/css/css-transforms/transform-input-018.html.ini2
-rw-r--r--tests/wpt/meta/css/css-transitions/pseudo-element-transform.html.ini2
-rw-r--r--tests/wpt/meta/css/css-transitions/transitions-retarget.html.ini3
-rw-r--r--tests/wpt/meta/css/css-values/calc-size/animation/interpolate-size-interpolation.html.ini2
-rw-r--r--tests/wpt/meta/css/css-values/calc-size/interpolate-size-computed.html.ini6
-rw-r--r--tests/wpt/meta/css/css-values/calc-size/interpolate-size-parsing.html.ini6
-rw-r--r--tests/wpt/meta/css/css-values/vh_not_refreshing_on_chrome.html.ini2
-rw-r--r--tests/wpt/meta/css/cssom-view/MediaQueryListEvent.html.ini3
-rw-r--r--tests/wpt/meta/dom/observable/tentative/observable-constructor.any.js.ini18
-rw-r--r--tests/wpt/meta/fetch/metadata/generated/element-img-environment-change.sub.html.ini3
-rw-r--r--tests/wpt/meta/html/browsers/history/the-history-interface/traverse_the_history_3.html.ini3
-rw-r--r--tests/wpt/meta/html/browsers/windows/embedded-opener-remove-frame.html.ini1
-rw-r--r--tests/wpt/meta/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-cross-origin.sub.html.ini4
-rw-r--r--tests/wpt/meta/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-same-origin.sub.html.ini4
-rw-r--r--tests/wpt/meta/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html.ini4
-rw-r--r--tests/wpt/meta/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html.ini4
-rw-r--r--tests/wpt/meta/html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html.ini4
-rw-r--r--tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-drawImage.html.ini1
-rw-r--r--tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-invalid-args.html.ini2
-rw-r--r--tests/wpt/meta/html/canvas/element/text/2d.text.measure.caret-position-edge-cases.tentative.html.ini3
-rw-r--r--tests/wpt/meta/html/canvas/element/text/2d.text.measure.caret-position-edges.tentative.html.ini30
-rw-r--r--tests/wpt/meta/html/canvas/element/text/2d.text.measure.caret-position.tentative.html.ini120
-rw-r--r--tests/wpt/meta/html/canvas/element/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html.ini2
-rw-r--r--tests/wpt/meta/html/canvas/element/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html.ini12
-rw-r--r--tests/wpt/meta/html/canvas/element/text/2d.text.measure.getActualBoundingBox.tentative.html.ini18
-rw-r--r--tests/wpt/meta/html/canvas/element/text/2d.text.measure.selection-rects.tentative.html.ini72
-rw-r--r--tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.html.ini3
-rw-r--r--tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.worker.js.ini3
-rw-r--r--tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.html.ini30
-rw-r--r--tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.worker.js.ini30
-rw-r--r--tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.caret-position.tentative.html.ini120
-rw-r--r--tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html.ini2
-rw-r--r--tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.worker.js.ini2
-rw-r--r--tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html.ini12
-rw-r--r--tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.worker.js.ini12
-rw-r--r--tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.html.ini18
-rw-r--r--tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.worker.js.ini18
-rw-r--r--tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.selection-rects.tentative.html.ini72
-rw-r--r--tests/wpt/meta/html/dom/elements/global-attributes/cdata-dir_auto.html.ini12
-rw-r--r--tests/wpt/meta/html/dom/elements/global-attributes/dir-assorted.window.js.ini3
-rw-r--r--tests/wpt/meta/html/dom/elements/global-attributes/dir-auto-dynamic-changes.window.js.ini24
-rw-r--r--tests/wpt/meta/html/dom/elements/global-attributes/dir-slots-directionality.html.ini9
-rw-r--r--tests/wpt/meta/html/semantics/embedded-content/media-elements/playing-the-media-resource/loop-from-ended.tentative.html.ini1
-rw-r--r--tests/wpt/meta/html/semantics/embedded-content/media-elements/track/track-element/no-cuechange-before-play.html.ini3
-rw-r--r--tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-1.html.ini2
-rw-r--r--tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-1.html.ini1
-rw-r--r--tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-2.html.ini2
-rw-r--r--tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-3.html.ini2
-rw-r--r--tests/wpt/meta/html/semantics/forms/form-submission-0/reparent-form-during-planned-navigation-task.html.ini4
-rw-r--r--tests/wpt/meta/html/semantics/invokers/idlharness.tentative.html.ini36
-rw-r--r--tests/wpt/meta/html/semantics/invokers/invokeelement-interface.tentative.html.ini30
-rw-r--r--tests/wpt/meta/resource-timing/test_resource_timing.https.html.ini3
-rw-r--r--tests/wpt/tests/.github/workflows/docker.yml2
-rw-r--r--tests/wpt/tests/.taskcluster.yml2
-rw-r--r--tests/wpt/tests/CODEOWNERS4
-rw-r--r--tests/wpt/tests/accname/name/comp_tooltip.html5
-rw-r--r--tests/wpt/tests/attribution-reporting/aggregatable-debug/simple-trigger-aggregatable-debug-report.sub.https.html1
-rw-r--r--tests/wpt/tests/credential-management/support/README.md21
-rw-r--r--tests/wpt/tests/css/CSS2/floats/crashtests/firefox-bug-1906768.html16
-rw-r--r--tests/wpt/tests/css/css-animations/display-none-prevents-starting-in-subtree.html35
-rw-r--r--tests/wpt/tests/css/css-break/box-decoration-break-clone-005.tentative.html15
-rw-r--r--tests/wpt/tests/css/css-break/box-decoration-break-clone-006.html17
-rw-r--r--tests/wpt/tests/css/css-break/box-decoration-break-clone-007.html9
-rw-r--r--tests/wpt/tests/css/css-break/box-decoration-break-clone-008.html14
-rw-r--r--tests/wpt/tests/css/css-break/box-decoration-break-clone-009-ref.html7
-rw-r--r--tests/wpt/tests/css/css-break/box-decoration-break-clone-009.html10
-rw-r--r--tests/wpt/tests/css/css-color/color-mix-non-srgb-001-ref.html2
-rw-r--r--tests/wpt/tests/css/css-color/color-mix-non-srgb-001.html2
-rw-r--r--tests/wpt/tests/css/css-contain/content-visibility/content-visibility-auto-print-ref.html4
-rw-r--r--tests/wpt/tests/css/css-contain/content-visibility/content-visibility-auto-print.html8
-rw-r--r--tests/wpt/tests/css/css-contain/content-visibility/content-visibility-auto-svg-image-ref.html11
-rw-r--r--tests/wpt/tests/css/css-contain/content-visibility/content-visibility-auto-svg-image.html25
-rw-r--r--tests/wpt/tests/css/css-masking/clip-path/animations/clip-path-animation-cancel-ref.html15
-rw-r--r--tests/wpt/tests/css/css-masking/clip-path/animations/clip-path-animation-cancel.html41
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-022.tentative.html33
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-023.tentative.html31
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-024.tentative.html46
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-025.tentative.html44
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-026.tentative.html24
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-027.tentative.html27
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-017.tentative.html23
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-018.tentative.html26
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-019.tentative.html27
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-020.tentative.html27
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-021.tentative.html35
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-022.tentative.html35
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-023.tentative.html35
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-024.tentative.html35
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-025.tentative.html35
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-026.tentative.html40
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-027.tentative.html25
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-028.tentative.html25
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-029.tentative.html26
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-030.tentative.html26
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-031.tentative.html49
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-032.tentative.html50
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-with-abspos-012.tentative.html35
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-with-abspos-013.tentative.html36
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-022-ref.html25
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-023-ref.html23
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-026-ref.html15
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-027-ref.html16
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-018-ref.html19
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-019-ref.html19
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-020-ref.html19
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-021-ref.html21
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-023-ref.html23
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-025-ref.html27
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-026-ref.html32
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-027-ref.html18
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-028-ref.html18
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-029-ref.html19
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-031-ref.html22
-rw-r--r--tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-032-ref.html34
-rw-r--r--tests/wpt/tests/css/css-page/page-background-001-print-ref.html12
-rw-r--r--tests/wpt/tests/css/css-page/page-background-001-print.html14
-rw-r--r--tests/wpt/tests/css/css-page/page-background-002-print-ref.html19
-rw-r--r--tests/wpt/tests/css/css-page/page-background-002-print.html21
-rw-r--r--tests/wpt/tests/css/css-page/page-background-003-print-ref.html25
-rw-r--r--tests/wpt/tests/css/css-page/page-background-003-print.html20
-rw-r--r--tests/wpt/tests/css/css-properties-values-api/registered-property-computation-color-004.html22
-rw-r--r--tests/wpt/tests/css/css-tables/collapsed-border-remove-row-group-ref.html24
-rw-r--r--tests/wpt/tests/css/css-tables/collapsed-border-remove-row-group.html39
-rw-r--r--tests/wpt/tests/css/css-transitions/pseudo-element-transform-ref.html21
-rw-r--r--tests/wpt/tests/css/css-transitions/pseudo-element-transform.html52
-rw-r--r--tests/wpt/tests/css/css-transitions/transitions-retarget.html35
-rw-r--r--tests/wpt/tests/css/css-values/calc-size/animation/interpolate-size-interpolation.html22
-rw-r--r--tests/wpt/tests/css/css-values/calc-size/interpolate-size-computed.html12
-rw-r--r--tests/wpt/tests/css/css-values/calc-size/interpolate-size-parsing.html16
-rw-r--r--tests/wpt/tests/custom-elements/form-associated/label-delegatesFocus.html26
-rw-r--r--tests/wpt/tests/dom/nodes/moveBefore/tentative/input-moveBefore-crash.html8
-rw-r--r--tests/wpt/tests/dom/observable/tentative/observable-constructor.any.js143
-rw-r--r--tests/wpt/tests/dom/observable/tentative/observable-filter.any.js5
-rw-r--r--tests/wpt/tests/dom/observable/tentative/observable-first.any.js4
-rw-r--r--tests/wpt/tests/dom/observable/tentative/observable-last.any.js4
-rw-r--r--tests/wpt/tests/dom/observable/tentative/observable-map.any.js2
-rw-r--r--tests/wpt/tests/dom/observable/tentative/observable-switchMap.any.js4
-rw-r--r--tests/wpt/tests/dom/observable/tentative/observable-takeUntil.any.js8
-rw-r--r--tests/wpt/tests/dom/observable/tentative/observable-toArray.any.js12
-rw-r--r--tests/wpt/tests/fedcm/META.yml3
-rw-r--r--tests/wpt/tests/fedcm/fedcm-abort.https.html (renamed from tests/wpt/tests/credential-management/fedcm-abort.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-after-abort.https.html (renamed from tests/wpt/tests/credential-management/fedcm-after-abort.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-authz/fedcm-continue-on-disallowed.https.html (renamed from tests/wpt/tests/credential-management/fedcm-authz/fedcm-continue-on-disallowed.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-authz/fedcm-continue-on-with-account.https.html (renamed from tests/wpt/tests/credential-management/fedcm-authz/fedcm-continue-on-with-account.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-authz/fedcm-continue-on.https.html (renamed from tests/wpt/tests/credential-management/fedcm-authz/fedcm-continue-on.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-authz/fedcm-disclosure-text-shown.https.html (renamed from tests/wpt/tests/credential-management/fedcm-authz/fedcm-disclosure-text-shown.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-authz/fedcm-userinfo-after-resolve.https.html (renamed from tests/wpt/tests/credential-management/fedcm-authz/fedcm-userinfo-after-resolve.https.html)2
-rw-r--r--tests/wpt/tests/fedcm/fedcm-authz/resolve-after-preventsilentaccess.https.html (renamed from tests/wpt/tests/credential-management/fedcm-authz/resolve-after-preventsilentaccess.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-auto-reauthn-without-approved-clients.https.html (renamed from tests/wpt/tests/credential-management/fedcm-auto-reauthn-without-approved-clients.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-auto-selected-flag.https.html (renamed from tests/wpt/tests/credential-management/fedcm-auto-selected-flag.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-basic.https.html (renamed from tests/wpt/tests/credential-management/fedcm-basic.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-button-and-other-account/fedcm-button-mode-basics.tentative.https.html (renamed from tests/wpt/tests/credential-management/fedcm-button-and-other-account/fedcm-button-mode-basics.tentative.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-button-and-other-account/fedcm-button-mode-priority.tentative.https.html (renamed from tests/wpt/tests/credential-management/fedcm-button-and-other-account/fedcm-button-mode-priority.tentative.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-button-and-other-account/fedcm-use-other-account-button-flow.tentative.https.html (renamed from tests/wpt/tests/credential-management/fedcm-button-and-other-account/fedcm-use-other-account-button-flow.tentative.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-button-and-other-account/fedcm-use-other-account.tentative.https.html (renamed from tests/wpt/tests/credential-management/fedcm-button-and-other-account/fedcm-use-other-account.tentative.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-client-metadata-not-cached.https.html (renamed from tests/wpt/tests/credential-management/fedcm-client-metadata-not-cached.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-context.https.html (renamed from tests/wpt/tests/credential-management/fedcm-context.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-cross-origin-policy.https.html (renamed from tests/wpt/tests/credential-management/fedcm-cross-origin-policy.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-cross-origin-policy.https.html.sub.headers (renamed from tests/wpt/tests/credential-management/fedcm-cross-origin-policy.https.html.sub.headers)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-csp.https.html (renamed from tests/wpt/tests/credential-management/fedcm-csp.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-csp.https.html.sub.headers (renamed from tests/wpt/tests/credential-management/fedcm-csp.https.html.sub.headers)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-disconnect-errors.https.html (renamed from tests/wpt/tests/credential-management/fedcm-disconnect-errors.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-disconnect-iframe.sub.https.html (renamed from tests/wpt/tests/credential-management/fedcm-disconnect-iframe.sub.https.html)4
-rw-r--r--tests/wpt/tests/fedcm/fedcm-disconnect.sub.https.html (renamed from tests/wpt/tests/credential-management/fedcm-disconnect.sub.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-domainhint.https.html (renamed from tests/wpt/tests/credential-management/fedcm-domainhint.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-endpoint-redirects.https.html (renamed from tests/wpt/tests/credential-management/fedcm-endpoint-redirects.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-error-basic.https.html (renamed from tests/wpt/tests/credential-management/fedcm-error-basic.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-identity-assertion-nocors.https.html (renamed from tests/wpt/tests/credential-management/fedcm-identity-assertion-nocors.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-iframe.https.html (renamed from tests/wpt/tests/credential-management/fedcm-iframe.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-login-status-unknown.https.html (renamed from tests/wpt/tests/credential-management/fedcm-login-status-unknown.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-login-status/confirm-idp-login.https.html (renamed from tests/wpt/tests/credential-management/fedcm-login-status/confirm-idp-login.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-login-status/cross-origin-status.https.html (renamed from tests/wpt/tests/credential-management/fedcm-login-status/cross-origin-status.https.html)4
-rw-r--r--tests/wpt/tests/fedcm/fedcm-login-status/logged-out.https.html (renamed from tests/wpt/tests/credential-management/fedcm-login-status/logged-out.https.html)2
-rw-r--r--tests/wpt/tests/fedcm/fedcm-loginhint.https.html (renamed from tests/wpt/tests/credential-management/fedcm-loginhint.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-manifest-not-in-list.https.html (renamed from tests/wpt/tests/credential-management/fedcm-manifest-not-in-list.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-multi-idp/fedcm-multi-idp-abort.https.html (renamed from tests/wpt/tests/credential-management/fedcm-multi-idp/fedcm-multi-idp-abort.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-multi-idp/fedcm-multi-idp-basic.https.html (renamed from tests/wpt/tests/credential-management/fedcm-multi-idp/fedcm-multi-idp-basic.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-multi-idp/fedcm-multi-idp-context.https.html (renamed from tests/wpt/tests/credential-management/fedcm-multi-idp/fedcm-multi-idp-context.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-multi-idp/fedcm-multi-idp-mediation-optional.https.html (renamed from tests/wpt/tests/credential-management/fedcm-multi-idp/fedcm-multi-idp-mediation-optional.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-multi-idp/fedcm-multi-idp-mediation-silent.https.html (renamed from tests/wpt/tests/credential-management/fedcm-multi-idp/fedcm-multi-idp-mediation-silent.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-no-login-url.https.html (renamed from tests/wpt/tests/credential-management/fedcm-no-login-url.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-nonce-is-optional.https.html (renamed from tests/wpt/tests/credential-management/fedcm-nonce-is-optional.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-not-observed-by-service-worker.https.html (renamed from tests/wpt/tests/credential-management/fedcm-not-observed-by-service-worker.https.html)2
-rw-r--r--tests/wpt/tests/fedcm/fedcm-opaque-rp-origin.https.html (renamed from tests/wpt/tests/credential-management/fedcm-opaque-rp-origin.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-opaque-rp-origin.https.html.headers (renamed from tests/wpt/tests/credential-management/fedcm-opaque-rp-origin.https.html.headers)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-pending-call-rejected.https.html (renamed from tests/wpt/tests/credential-management/fedcm-pending-call-rejected.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-pending-disconnect.https.html (renamed from tests/wpt/tests/credential-management/fedcm-pending-disconnect.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-pending-userinfo.https.html (renamed from tests/wpt/tests/credential-management/fedcm-pending-userinfo.https.html)2
-rw-r--r--tests/wpt/tests/fedcm/fedcm-register/fedcm-no-registered-idps.https.html (renamed from tests/wpt/tests/credential-management/fedcm-register/fedcm-no-registered-idps.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-reject-invalid-responses.https.html (renamed from tests/wpt/tests/credential-management/fedcm-reject-invalid-responses.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-returning-account-auto-reauthn.https.html (renamed from tests/wpt/tests/credential-management/fedcm-returning-account-auto-reauthn.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-same-site-none/fedcm-same-site-none.https.html (renamed from tests/wpt/tests/credential-management/fedcm-same-site-none/fedcm-same-site-none.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-store.https.html (renamed from tests/wpt/tests/credential-management/fedcm-store.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-token-returned-with-http-error.https.html (renamed from tests/wpt/tests/credential-management/fedcm-token-returned-with-http-error.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-too-many-disconnect-calls.https.html (renamed from tests/wpt/tests/credential-management/fedcm-too-many-disconnect-calls.https.html)0
-rw-r--r--tests/wpt/tests/fedcm/fedcm-userinfo.https.html (renamed from tests/wpt/tests/credential-management/fedcm-userinfo.https.html)4
-rw-r--r--tests/wpt/tests/fedcm/support/README.md20
-rw-r--r--tests/wpt/tests/fedcm/support/accounts_check_same_site_strict.py1
-rw-r--r--tests/wpt/tests/fedcm/support/fedcm-helper.sub.js (renamed from tests/wpt/tests/credential-management/support/fedcm-helper.sub.js)7
-rw-r--r--tests/wpt/tests/fedcm/support/fedcm-helper.sub.js.headers (renamed from tests/wpt/tests/credential-management/support/fedcm-helper.sub.js.headers)0
-rw-r--r--tests/wpt/tests/fedcm/support/fedcm-iframe-level2.html (renamed from tests/wpt/tests/credential-management/support/fedcm-iframe-level2.html)0
-rw-r--r--tests/wpt/tests/fedcm/support/fedcm-iframe.html (renamed from tests/wpt/tests/credential-management/support/fedcm-iframe.html)0
-rw-r--r--tests/wpt/tests/fedcm/support/fedcm-mock.js (renamed from tests/wpt/tests/credential-management/support/fedcm-mock.js)0
-rw-r--r--tests/wpt/tests/fedcm/support/fedcm-mojojs-helper.js (renamed from tests/wpt/tests/credential-management/support/fedcm-mojojs-helper.js)0
-rw-r--r--tests/wpt/tests/fedcm/support/fedcm/disconnect-iframe.html (renamed from tests/wpt/tests/credential-management/support/fedcm/disconnect-iframe.html)0
-rw-r--r--tests/wpt/tests/fedcm/support/fedcm/intercept_service_worker.js (renamed from tests/wpt/tests/credential-management/support/fedcm/intercept_service_worker.js)3
-rw-r--r--tests/wpt/tests/fedcm/support/fedcm/pending-userinfo-iframe.html (renamed from tests/wpt/tests/credential-management/support/fedcm/pending-userinfo-iframe.html)0
-rw-r--r--tests/wpt/tests/fedcm/support/fedcm/simple.html (renamed from tests/wpt/tests/credential-management/support/fedcm/simple.html)0
-rw-r--r--tests/wpt/tests/fedcm/support/fedcm/userinfo-iframe.html (renamed from tests/wpt/tests/credential-management/support/fedcm/userinfo-iframe.html)0
-rw-r--r--tests/wpt/tests/fedcm/support/fencedframe-mark-signedin.html (renamed from tests/wpt/tests/credential-management/support/fencedframe-mark-signedin.html)0
-rw-r--r--tests/wpt/tests/fedcm/support/iframe-mark-signedin.html (renamed from tests/wpt/tests/credential-management/support/iframe-mark-signedin.html)0
-rw-r--r--tests/wpt/tests/fedcm/support/mark_signedin (renamed from tests/wpt/tests/credential-management/support/mark_signedin)0
-rw-r--r--tests/wpt/tests/fedcm/support/mark_signedin.sub.headers (renamed from tests/wpt/tests/credential-management/support/mark_signedin.sub.headers)0
-rw-r--r--tests/wpt/tests/fedcm/support/mark_signedout (renamed from tests/wpt/tests/credential-management/support/mark_signedout)0
-rw-r--r--tests/wpt/tests/fedcm/support/mark_signedout.sub.headers (renamed from tests/wpt/tests/credential-management/support/mark_signedout.sub.headers)0
-rw-r--r--tests/wpt/tests/fedcm/support/set_cookie (renamed from tests/wpt/tests/credential-management/support/set_cookie)0
-rw-r--r--tests/wpt/tests/fedcm/support/set_cookie.headers (renamed from tests/wpt/tests/credential-management/support/set_cookie.headers)0
-rw-r--r--tests/wpt/tests/fedcm/support/token_check_same_site_strict.py1
-rw-r--r--tests/wpt/tests/fetch/metadata/resources/post-to-owner.py12
-rw-r--r--tests/wpt/tests/geolocation/PositionOptions.https.html35
-rw-r--r--tests/wpt/tests/geolocation/tojson.https.window.js58
-rw-r--r--tests/wpt/tests/hr-time/raf-coarsened-time.https.html2
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/first-party-to-first-party-cross-partition.sub.html26
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/first-party-to-first-party-same-partition.html26
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-cross-origin.sub.html27
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-same-origin.sub.html27
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-first-party-cross-partition-window.html8
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-first-party-same-partition-window.html8
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-cross-origin-iframe.https.html8
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-cross-origin-window.sub.https.html6
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-same-origin-iframe.html8
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-same-origin-window.sub.html6
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-cross-origin-iframe.sub.html17
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-cross-origin-window.https.html8
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-same-origin-iframe.sub.html17
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-same-origin-window.html8
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-iframe-a.sub.html17
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-iframe-b.https.html8
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-window.sub.https.html6
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-iframe-a.sub.https.html17
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-iframe-b.https.html8
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-window.sub.https.html6
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-iframe-a.sub.html17
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-iframe-b.html8
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-window.sub.html6
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/third-party-to-first-party-cross-partition-cross-origin.sub.html29
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/third-party-to-first-party-cross-partition-same-origin.sub.html29
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html30
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html30
-rw-r--r--tests/wpt/tests/html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html30
-rw-r--r--tests/wpt/tests/html/canvas/element/text/2d.text.measure.caret-position-edge-cases.tentative.html58
-rw-r--r--tests/wpt/tests/html/canvas/element/text/2d.text.measure.caret-position-edges.tentative.html714
-rw-r--r--tests/wpt/tests/html/canvas/element/text/2d.text.measure.caret-position.tentative.html4074
-rw-r--r--tests/wpt/tests/html/canvas/element/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html45
-rw-r--r--tests/wpt/tests/html/canvas/element/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html278
-rw-r--r--tests/wpt/tests/html/canvas/element/text/2d.text.measure.getActualBoundingBox.tentative.html502
-rw-r--r--tests/wpt/tests/html/canvas/element/text/2d.text.measure.selection-rects-baselines.tentative.html3
-rw-r--r--tests/wpt/tests/html/canvas/element/text/2d.text.measure.selection-rects-exceptions.tentative.html3
-rw-r--r--tests/wpt/tests/html/canvas/element/text/2d.text.measure.selection-rects.tentative.html2765
-rw-r--r--tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.html47
-rw-r--r--tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.worker.js41
-rw-r--r--tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.html712
-rw-r--r--tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.worker.js709
-rw-r--r--tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.caret-position.tentative.html4072
-rw-r--r--tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html46
-rw-r--r--tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.worker.js41
-rw-r--r--tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html276
-rw-r--r--tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.worker.js273
-rw-r--r--tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.html510
-rw-r--r--tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.worker.js507
-rw-r--r--tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects-baselines.tentative.html3
-rw-r--r--tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects-baselines.tentative.worker.js3
-rw-r--r--tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects-exceptions.tentative.html3
-rw-r--r--tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects-exceptions.tentative.worker.js3
-rw-r--r--tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects.tentative.html2766
-rw-r--r--tests/wpt/tests/html/canvas/tools/yaml-new/text.yaml659
-rw-r--r--tests/wpt/tests/html/cross-origin-opener-policy/reporting/resources/reporting-common.js2
-rw-r--r--tests/wpt/tests/html/cross-origin-opener-policy/reporting/tentative/access-to-noopener-page-from-no-coop-ro.https.html69
-rw-r--r--tests/wpt/tests/html/cross-origin-opener-policy/resources/noopener-helper.js163
-rw-r--r--tests/wpt/tests/html/cross-origin-opener-policy/tentative/noopener/coop-noopener-allow-popups-restrict-properties.https.html24
-rw-r--r--tests/wpt/tests/html/cross-origin-opener-policy/tentative/noopener/coop-noopener-allow-popups.https.html36
-rw-r--r--tests/wpt/tests/html/dom/elements/global-attributes/cdata-dir_auto.html71
-rw-r--r--tests/wpt/tests/html/dom/elements/global-attributes/cdata-iframe.xhtml32
-rw-r--r--tests/wpt/tests/html/dom/elements/global-attributes/dir-assorted.window.js34
-rw-r--r--tests/wpt/tests/html/dom/elements/global-attributes/dir-auto-dynamic-changes.window.js196
-rw-r--r--tests/wpt/tests/html/dom/elements/global-attributes/dir-shadow-utils.js16
-rw-r--r--tests/wpt/tests/html/dom/elements/global-attributes/dir-slots-directionality.html237
-rw-r--r--tests/wpt/tests/html/rendering/widgets/input-checkbox-appearance-none-dynamic-crash.html16
-rw-r--r--tests/wpt/tests/html/semantics/forms/constraints/form-validation-reportValidity.html1
-rw-r--r--tests/wpt/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-menu-ref.html23
-rw-r--r--tests/wpt/tests/html/semantics/interactive-elements/the-details-element/name-attribute.html4
-rw-r--r--tests/wpt/tests/html/semantics/invokers/idlharness.tentative.html2
-rw-r--r--tests/wpt/tests/html/semantics/invokers/invokeelement-interface.tentative.html93
-rw-r--r--tests/wpt/tests/html/semantics/invokers/invokeevent-dispatch-shadow.tentative.html19
-rw-r--r--tests/wpt/tests/html/semantics/invokers/invokeevent-interface.tentative.html116
-rw-r--r--tests/wpt/tests/html/semantics/invokers/invoketarget-button-event-dispatch.tentative.html138
-rw-r--r--tests/wpt/tests/html/semantics/invokers/invoketarget-fullscreen-behavior.tentative.html99
-rw-r--r--tests/wpt/tests/html/semantics/invokers/invoketarget-on-audio-behavior.tentative.html159
-rw-r--r--tests/wpt/tests/html/semantics/invokers/invoketarget-on-audio-invalid-behavior.tentative.html7
-rw-r--r--tests/wpt/tests/html/semantics/invokers/invoketarget-on-details-behavior.tentative.html54
-rw-r--r--tests/wpt/tests/html/semantics/invokers/invoketarget-on-details-invalid-behavior.tentative.html15
-rw-r--r--tests/wpt/tests/html/semantics/invokers/invoketarget-on-dialog-behavior.tentative.html190
-rw-r--r--tests/wpt/tests/html/semantics/invokers/invoketarget-on-dialog-invalid-behavior.tentative.html27
-rw-r--r--tests/wpt/tests/html/semantics/invokers/invoketarget-on-input-number.tentative.html20
-rw-r--r--tests/wpt/tests/html/semantics/invokers/invoketarget-on-popover-behavior.tentative.html94
-rw-r--r--tests/wpt/tests/html/semantics/invokers/invoketarget-on-popover-invalid-behavior.tentative.html18
-rw-r--r--tests/wpt/tests/html/semantics/invokers/invoketarget-on-video-behavior.tentative.html76
-rw-r--r--tests/wpt/tests/html/semantics/popovers/popover-attribute-basic.html25
-rw-r--r--tests/wpt/tests/interfaces/invokers.tentative.idl14
-rw-r--r--tests/wpt/tests/intersection-observer/v2/box-reflect.html50
-rw-r--r--tests/wpt/tests/lint.ignore8
-rw-r--r--tests/wpt/tests/long-animation-frame/tentative/loaf-pointer-without-render-iframe.html57
-rw-r--r--tests/wpt/tests/long-animation-frame/tentative/loaf-pointer-without-render.html38
-rw-r--r--tests/wpt/tests/long-animation-frame/tentative/resources/long-pointerdown.html26
-rw-r--r--tests/wpt/tests/long-animation-frame/tentative/resources/utils.js8
-rw-r--r--tests/wpt/tests/mathml/presentation-markup/mrow/mrow-fallback.html4
-rw-r--r--tests/wpt/tests/mathml/relations/css-styling/padding-border-margin/padding-border-margin-004-ref.html26
-rw-r--r--tests/wpt/tests/mathml/relations/css-styling/padding-border-margin/padding-border-margin-004.html33
-rw-r--r--tests/wpt/tests/permissions-policy/payment-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html3
-rw-r--r--tests/wpt/tests/resources/chromium/web-bluetooth-test.js2
-rw-r--r--tests/wpt/tests/sanitizer-api/sanitizer-names.https.html2
-rw-r--r--tests/wpt/tests/selection/caret/after-designMode-off-ref.html11
-rw-r--r--tests/wpt/tests/selection/caret/after-designMode-off.html25
-rw-r--r--tests/wpt/tests/selection/onselectionchange-on-document.html8
-rw-r--r--tests/wpt/tests/shadow-dom/selection-direction.tentative.html59
-rw-r--r--tests/wpt/tests/storage-access-api/resources/storage-access-beyond-cookies-iframe-iframe.html4
-rw-r--r--tests/wpt/tests/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html4
-rw-r--r--tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.BroadcastChannel.sub.https.window.js (renamed from tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.BroadcastChannel.tentative.sub.https.window.js)0
-rw-r--r--tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.SharedWorker.sub.https.window.js (renamed from tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.SharedWorker.tentative.sub.https.window.js)1
-rw-r--r--tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.blobStorage.sub.https.window.js (renamed from tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.blobStorage.tentative.sub.https.window.js)0
-rw-r--r--tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.caches.sub.https.window.js (renamed from tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.caches.tentative.sub.https.window.js)0
-rw-r--r--tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.cookies.sub.https.window.js (renamed from tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.cookies.tentative.sub.https.window.js)0
-rw-r--r--tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.estimate.sub.https.window.js (renamed from tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.estimate.tentative.sub.https.window.js)0
-rw-r--r--tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.getDirectory.sub.https.window.js (renamed from tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.getDirectory.tentative.sub.https.window.js)0
-rw-r--r--tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.indexedDB.sub.https.window.js (renamed from tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.indexedDB.tentative.sub.https.window.js)0
-rw-r--r--tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.localStorage.sub.https.window.js (renamed from tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.localStorage.tentative.sub.https.window.js)0
-rw-r--r--tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.locks.sub.https.window.js (renamed from tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.locks.tentative.sub.https.window.js)0
-rw-r--r--tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.none.sub.https.window.js (renamed from tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.none.tentative.sub.https.window.js)0
-rw-r--r--tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.sessionStorage.sub.https.window.js (renamed from tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.sessionStorage.tentative.sub.https.window.js)0
-rw-r--r--tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.unpartitioned.sub.https.window.js (renamed from tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.unpartitioned.tentative.sub.https.window.js)0
-rw-r--r--tests/wpt/tests/svg/painting/reftests/non-scaling-stroke-003.html22
-rw-r--r--tests/wpt/tests/svg/painting/reftests/symbol-in-mask.html31
-rw-r--r--tests/wpt/tests/svg/styling/vector-effect-invalid.html3
-rw-r--r--tests/wpt/tests/svg/styling/vector-effect-valid.html19
-rw-r--r--tests/wpt/tests/tools/ci/requirements_tc.txt2
-rw-r--r--tests/wpt/tests/tools/ci/tc/tasks/test.yml2
-rw-r--r--tests/wpt/tests/tools/docker/README.md15
-rw-r--r--tests/wpt/tests/tools/docker/frontend.py21
-rw-r--r--tests/wpt/tests/tools/requirements_mypy.txt2
-rw-r--r--tests/wpt/tests/tools/requirements_pytest.txt2
-rw-r--r--tests/wpt/tests/tools/requirements_tests.txt2
-rw-r--r--tests/wpt/tests/tools/webdriver/webdriver/bidi/client.py2
-rw-r--r--tests/wpt/tests/tools/webdriver/webdriver/bidi/modules/network.py15
-rw-r--r--tests/wpt/tests/tools/webdriver/webdriver/transport.py16
-rw-r--r--tests/wpt/tests/tools/wptrunner/wptrunner/testharnessreport-content-shell.js45
-rw-r--r--tests/wpt/tests/webcodecs/audio-encoder-config.https.any.js9
-rw-r--r--tests/wpt/tests/webcodecs/full-cycle-test.https.any.js14
-rw-r--r--tests/wpt/tests/webcodecs/video-encoder-config.https.any.js9
-rw-r--r--tests/wpt/tests/webcodecs/videoFrame-copyTo-rgb.any.js96
-rw-r--r--tests/wpt/tests/webdriver/tests/bidi/browsing_context/close/prompt_unload.py2
-rw-r--r--tests/wpt/tests/webdriver/tests/bidi/browsing_context/handle_user_prompt/handle_user_prompt.py2
-rw-r--r--tests/wpt/tests/webdriver/tests/bidi/browsing_context/user_prompt_closed/beforeunload.py2
-rw-r--r--tests/wpt/tests/webdriver/tests/bidi/browsing_context/user_prompt_opened/user_prompt_opened.py66
-rw-r--r--tests/wpt/tests/webdriver/tests/bidi/integration/cookies_with_network_events.py2
-rw-r--r--tests/wpt/tests/webdriver/tests/bidi/network/set_cache_behavior/__init__.py (renamed from tests/wpt/tests/webdriver/tests/bidi/network/set_cache_bypass/__init__.py)0
-rw-r--r--tests/wpt/tests/webdriver/tests/bidi/network/set_cache_behavior/conftest.py33
-rw-r--r--tests/wpt/tests/webdriver/tests/bidi/network/set_cache_behavior/contexts.py204
-rw-r--r--tests/wpt/tests/webdriver/tests/bidi/network/set_cache_behavior/invalid.py39
-rw-r--r--tests/wpt/tests/webdriver/tests/bidi/network/set_cache_behavior/set_cache_behavior.py51
-rw-r--r--tests/wpt/tests/webdriver/tests/bidi/network/set_cache_bypass/conftest.py17
-rw-r--r--tests/wpt/tests/webdriver/tests/bidi/network/set_cache_bypass/contexts_tentative.py99
-rw-r--r--tests/wpt/tests/webdriver/tests/bidi/network/set_cache_bypass/invalid_tentative.py32
-rw-r--r--tests/wpt/tests/webdriver/tests/bidi/network/set_cache_bypass/set_cache_bypass_tentative.py68
-rw-r--r--tests/wpt/tests/webdriver/tests/classic/new_session/unhandled_prompt_behavior.py10
-rw-r--r--tests/wpt/tests/webnn/resources/test_data/conv_transpose2d.json49
-rw-r--r--tests/wpt/tests/webnn/resources/utils.js36
-rw-r--r--tests/wpt/tests/webnn/validation_tests/convTranspose2d.https.any.js20
-rw-r--r--tests/wpt/tests/webnn/validation_tests/gru.https.any.js2
-rw-r--r--tests/wpt/tests/webnn/validation_tests/gruCell.https.any.js2
-rw-r--r--tests/wpt/tests/webnn/validation_tests/lstm.https.any.js2
-rw-r--r--tests/wpt/tests/webnn/validation_tests/lstmCell.https.any.js2
-rw-r--r--tests/wpt/tests/webnn/validation_tests/pooling.https.any.js20
-rw-r--r--tests/wpt/tests/webnn/validation_tests/resample2d.https.any.js10
-rw-r--r--tests/wpt/tests/webnn/validation_tests/split.https.any.js19
-rw-r--r--tests/wpt/tests/webnn/validation_tests/where.https.any.js122
513 files changed, 26697 insertions, 3183 deletions
diff --git a/tests/wpt/meta-legacy-layout/css/compositing/mix-blend-mode/mix-blend-mode-video.html.ini b/tests/wpt/meta-legacy-layout/css/compositing/mix-blend-mode/mix-blend-mode-video.html.ini
deleted file mode 100644
index 6c64ea15c62..00000000000
--- a/tests/wpt/meta-legacy-layout/css/compositing/mix-blend-mode/mix-blend-mode-video.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[mix-blend-mode-video.html]
- type: reftest
- expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-animations/display-none-prevents-starting-in-subtree.html.ini b/tests/wpt/meta-legacy-layout/css/css-animations/display-none-prevents-starting-in-subtree.html.ini
new file mode 100644
index 00000000000..612a75448bb
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-animations/display-none-prevents-starting-in-subtree.html.ini
@@ -0,0 +1,3 @@
+[display-none-prevents-starting-in-subtree.html]
+ [Elements in a "display: none" tree cannot start CSS Animations.]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-fonts/generic-family-keywords-001.html.ini b/tests/wpt/meta-legacy-layout/css/css-fonts/generic-family-keywords-001.html.ini
index 07b3a85a5a5..185d50ed83a 100644
--- a/tests/wpt/meta-legacy-layout/css/css-fonts/generic-family-keywords-001.html.ini
+++ b/tests/wpt/meta-legacy-layout/css/css-fonts/generic-family-keywords-001.html.ini
@@ -5,15 +5,9 @@
[@font-face matching for quoted and unquoted sans-serif]
expected: FAIL
- [@font-face matching for quoted and unquoted fantasy]
- expected: FAIL
-
[@font-face matching for quoted and unquoted monospace]
expected: FAIL
- [@font-face matching for quoted and unquoted system-ui]
- expected: FAIL
-
[@font-face matching for quoted and unquoted emoji]
expected: FAIL
@@ -32,8 +26,5 @@
[@font-face matching for quoted and unquoted ui-monospace]
expected: FAIL
- [@font-face matching for quoted and unquoted cursive]
- expected: FAIL
-
[@font-face matching for quoted and unquoted ui-serif]
expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-fonts/parsing/font-family-valid.html.ini b/tests/wpt/meta-legacy-layout/css/css-fonts/parsing/font-family-valid.html.ini
deleted file mode 100644
index b17ac3de579..00000000000
--- a/tests/wpt/meta-legacy-layout/css/css-fonts/parsing/font-family-valid.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[font-family-valid.html]
- [e.style['font-family'\] = "System-UI" should set the property value]
- expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-fonts/system-ui-ar.html.ini b/tests/wpt/meta-legacy-layout/css/css-fonts/system-ui-ar.html.ini
deleted file mode 100644
index d334315178a..00000000000
--- a/tests/wpt/meta-legacy-layout/css/css-fonts/system-ui-ar.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[system-ui-ar.html]
- expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-fonts/system-ui-mixed.html.ini b/tests/wpt/meta-legacy-layout/css/css-fonts/system-ui-mixed.html.ini
deleted file mode 100644
index 9965271ed89..00000000000
--- a/tests/wpt/meta-legacy-layout/css/css-fonts/system-ui-mixed.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[system-ui-mixed.html]
- expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-fonts/system-ui-ur.html.ini b/tests/wpt/meta-legacy-layout/css/css-fonts/system-ui-ur.html.ini
deleted file mode 100644
index d9e58e116a2..00000000000
--- a/tests/wpt/meta-legacy-layout/css/css-fonts/system-ui-ur.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[system-ui-ur.html]
- expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-fonts/system-ui.html.ini b/tests/wpt/meta-legacy-layout/css/css-fonts/system-ui.html.ini
deleted file mode 100644
index fc1193a36c4..00000000000
--- a/tests/wpt/meta-legacy-layout/css/css-fonts/system-ui.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[system-ui.html]
- expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-022.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-022.tentative.html.ini
new file mode 100644
index 00000000000..b9715bdcbcf
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-022.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-022.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-023.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-023.tentative.html.ini
new file mode 100644
index 00000000000..4de92d88f8a
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-023.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-023.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-024.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-024.tentative.html.ini
new file mode 100644
index 00000000000..b4f17815816
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-024.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-024.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-025.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-025.tentative.html.ini
new file mode 100644
index 00000000000..21c21386446
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-025.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-025.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-026.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-026.tentative.html.ini
new file mode 100644
index 00000000000..cc760d52775
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-026.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-026.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-027.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-027.tentative.html.ini
new file mode 100644
index 00000000000..72c0f91de75
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-027.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-027.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-017.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-017.tentative.html.ini
new file mode 100644
index 00000000000..50a860f5cff
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-017.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-017.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-018.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-018.tentative.html.ini
new file mode 100644
index 00000000000..cc12506ab76
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-018.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-018.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-019.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-019.tentative.html.ini
new file mode 100644
index 00000000000..0eb22442455
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-019.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-019.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-020.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-020.tentative.html.ini
new file mode 100644
index 00000000000..c4a3171091e
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-020.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-020.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-021.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-021.tentative.html.ini
new file mode 100644
index 00000000000..ec0dd2607ab
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-021.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-021.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-022.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-022.tentative.html.ini
new file mode 100644
index 00000000000..a4378bd91ec
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-022.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-022.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-023.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-023.tentative.html.ini
new file mode 100644
index 00000000000..be570e7abe9
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-023.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-023.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-024.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-024.tentative.html.ini
new file mode 100644
index 00000000000..cfa4a650a3a
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-024.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-024.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-025.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-025.tentative.html.ini
new file mode 100644
index 00000000000..c1786082c26
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-025.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-025.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-026.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-026.tentative.html.ini
new file mode 100644
index 00000000000..0ee497e3bcd
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-026.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-026.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-027.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-027.tentative.html.ini
new file mode 100644
index 00000000000..30b109549c3
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-027.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-027.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-028.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-028.tentative.html.ini
new file mode 100644
index 00000000000..465e96a5404
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-028.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-028.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-029.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-029.tentative.html.ini
new file mode 100644
index 00000000000..3f6611a7627
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-029.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-029.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-030.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-030.tentative.html.ini
new file mode 100644
index 00000000000..4fdd099a51a
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-030.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-030.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-031.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-031.tentative.html.ini
new file mode 100644
index 00000000000..2c233b1cacc
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-031.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-031.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-032.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-032.tentative.html.ini
new file mode 100644
index 00000000000..00093f16b28
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-auto-032.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-032.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-with-abspos-012.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-with-abspos-012.tentative.html.ini
new file mode 100644
index 00000000000..2f746ad56f3
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-with-abspos-012.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-with-abspos-012.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-with-abspos-013.tentative.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-with-abspos-013.tentative.html.ini
new file mode 100644
index 00000000000..f710874fa75
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/line-clamp/line-clamp-with-abspos-013.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-with-abspos-013.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-overflow/overflow-video.html.ini b/tests/wpt/meta-legacy-layout/css/css-overflow/overflow-video.html.ini
new file mode 100644
index 00000000000..1d0a9d754d6
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-overflow/overflow-video.html.ini
@@ -0,0 +1,2 @@
+[overflow-video.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-transforms/transform-input-017.html.ini b/tests/wpt/meta-legacy-layout/css/css-transforms/transform-input-017.html.ini
index b6c96fff2e3..66db39edcb9 100644
--- a/tests/wpt/meta-legacy-layout/css/css-transforms/transform-input-017.html.ini
+++ b/tests/wpt/meta-legacy-layout/css/css-transforms/transform-input-017.html.ini
@@ -1,5 +1,3 @@
[transform-input-017.html]
type: reftest
bug: https://github.com/servo/servo/issues/21092
- expected:
- if os == "linux": FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-transforms/transform-input-018.html.ini b/tests/wpt/meta-legacy-layout/css/css-transforms/transform-input-018.html.ini
index c90ddb7f550..894e4cca09e 100644
--- a/tests/wpt/meta-legacy-layout/css/css-transforms/transform-input-018.html.ini
+++ b/tests/wpt/meta-legacy-layout/css/css-transforms/transform-input-018.html.ini
@@ -1,4 +1,3 @@
[transform-input-018.html]
type: reftest
bug: https://github.com/servo/servo/issues/21092
- expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-transitions/pseudo-element-transform.html.ini b/tests/wpt/meta-legacy-layout/css/css-transitions/pseudo-element-transform.html.ini
new file mode 100644
index 00000000000..67df2295e2e
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-transitions/pseudo-element-transform.html.ini
@@ -0,0 +1,2 @@
+[pseudo-element-transform.html]
+ expected: TIMEOUT
diff --git a/tests/wpt/meta-legacy-layout/css/css-transitions/transitions-retarget.html.ini b/tests/wpt/meta-legacy-layout/css/css-transitions/transitions-retarget.html.ini
new file mode 100644
index 00000000000..72d9f209146
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-transitions/transitions-retarget.html.ini
@@ -0,0 +1,3 @@
+[transitions-retarget.html]
+ [Retargeting a transition should cause the old transition to be cancelled]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-values/calc-size/animation/interpolate-size-interpolation.html.ini b/tests/wpt/meta-legacy-layout/css/css-values/calc-size/animation/interpolate-size-interpolation.html.ini
new file mode 100644
index 00000000000..3c23c23b88a
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-values/calc-size/animation/interpolate-size-interpolation.html.ini
@@ -0,0 +1,2 @@
+[interpolate-size-interpolation.html]
+ expected: ERROR
diff --git a/tests/wpt/meta-legacy-layout/css/css-values/calc-size/interpolate-size-computed.html.ini b/tests/wpt/meta-legacy-layout/css/css-values/calc-size/interpolate-size-computed.html.ini
new file mode 100644
index 00000000000..ae2404f8a21
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-values/calc-size/interpolate-size-computed.html.ini
@@ -0,0 +1,6 @@
+[interpolate-size-computed.html]
+ [Property interpolate-size value 'numeric-only']
+ expected: FAIL
+
+ [Property interpolate-size value 'allow-keywords']
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/css-values/calc-size/interpolate-size-parsing.html.ini b/tests/wpt/meta-legacy-layout/css/css-values/calc-size/interpolate-size-parsing.html.ini
new file mode 100644
index 00000000000..61bfc890ca0
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/css/css-values/calc-size/interpolate-size-parsing.html.ini
@@ -0,0 +1,6 @@
+[interpolate-size-parsing.html]
+ [e.style['interpolate-size'\] = "numeric-only" should set the property value]
+ expected: FAIL
+
+ [e.style['interpolate-size'\] = "allow-keywords" should set the property value]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/css/cssom-view/MediaQueryListEvent.html.ini b/tests/wpt/meta-legacy-layout/css/cssom-view/MediaQueryListEvent.html.ini
index 5276a0fea9f..0fb25fc1fba 100644
--- a/tests/wpt/meta-legacy-layout/css/cssom-view/MediaQueryListEvent.html.ini
+++ b/tests/wpt/meta-legacy-layout/css/cssom-view/MediaQueryListEvent.html.ini
@@ -1,2 +1,7 @@
[MediaQueryListEvent.html]
expected: TIMEOUT
+ [argument of onchange]
+ expected: TIMEOUT
+
+ [constructor of "change" event]
+ expected: NOTRUN
diff --git a/tests/wpt/meta-legacy-layout/css/selectors/media/media-playback-state.html.ini b/tests/wpt/meta-legacy-layout/css/selectors/media/media-playback-state.html.ini
index 5ece18229f8..0d01e57bd5f 100644
--- a/tests/wpt/meta-legacy-layout/css/selectors/media/media-playback-state.html.ini
+++ b/tests/wpt/meta-legacy-layout/css/selectors/media/media-playback-state.html.ini
@@ -1,4 +1,5 @@
[media-playback-state.html]
+ expected: TIMEOUT
[Test :pseudo-class syntax is supported without throwing a SyntaxError]
expected: FAIL
@@ -6,7 +7,7 @@
expected: FAIL
[Test :paused pseudo-classes]
- expected: FAIL
+ expected: TIMEOUT
[Test :seeking pseudo-class]
- expected: FAIL
+ expected: NOTRUN
diff --git a/tests/wpt/meta-legacy-layout/custom-elements/CustomElementRegistry-getName.html.ini b/tests/wpt/meta-legacy-layout/custom-elements/CustomElementRegistry-getName.html.ini
deleted file mode 100644
index cb43c25dc3a..00000000000
--- a/tests/wpt/meta-legacy-layout/custom-elements/CustomElementRegistry-getName.html.ini
+++ /dev/null
@@ -1,9 +0,0 @@
-[CustomElementRegistry-getName.html]
- [customElements.getName must return null when the registry does not contain an entry with the given constructor]
- expected: FAIL
-
- [customElements.getName returns the name of the entry with the given constructor when there is a matching entry.]
- expected: FAIL
-
- [customElements.getName returns the name of the entry with the given customized built in constructor when there is a matching entry.]
- expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/dom/observable/tentative/observable-constructor.any.js.ini b/tests/wpt/meta-legacy-layout/dom/observable/tentative/observable-constructor.any.js.ini
index 3997fbb757a..8221c036594 100644
--- a/tests/wpt/meta-legacy-layout/dom/observable/tentative/observable-constructor.any.js.ini
+++ b/tests/wpt/meta-legacy-layout/dom/observable/tentative/observable-constructor.any.js.ini
@@ -110,6 +110,15 @@
[Subscriber#error() value is stored as Subscriber's AbortSignal's reason]
expected: FAIL
+ [Teardowns are called in upstream->downstream order on consumer-initiated unsubscription]
+ expected: FAIL
+
+ [Teardowns are called in downstream->upstream order on consumer-initiated unsubscription with pre-aborted Signal]
+ expected: FAIL
+
+ [Producer-initiated unsubscription in a downstream Observable fires abort events before each teardown, in downstream->upstream order]
+ expected: FAIL
+
[observable-constructor.any.html]
[Observable constructor]
@@ -222,3 +231,12 @@
[Subscriber#error() value is stored as Subscriber's AbortSignal's reason]
expected: FAIL
+
+ [Teardowns are called in upstream->downstream order on consumer-initiated unsubscription]
+ expected: FAIL
+
+ [Teardowns are called in downstream->upstream order on consumer-initiated unsubscription with pre-aborted Signal]
+ expected: FAIL
+
+ [Producer-initiated unsubscription in a downstream Observable fires abort events before each teardown, in downstream->upstream order]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/fetch/metadata/generated/css-font-face.sub.tentative.html.ini b/tests/wpt/meta-legacy-layout/fetch/metadata/generated/css-font-face.sub.tentative.html.ini
index 6eebff7271d..349ae97cf12 100644
--- a/tests/wpt/meta-legacy-layout/fetch/metadata/generated/css-font-face.sub.tentative.html.ini
+++ b/tests/wpt/meta-legacy-layout/fetch/metadata/generated/css-font-face.sub.tentative.html.ini
@@ -32,8 +32,5 @@
[sec-fetch-dest - Not sent to non-trustworthy same-site destination]
expected: FAIL
- [sec-fetch-user - Not sent to non-trustworthy same-site destination]
- expected: FAIL
-
[sec-fetch-user - Not sent to non-trustworthy cross-site destination]
expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/fetch/orb/tentative/known-mime-type.sub.any.js.ini b/tests/wpt/meta-legacy-layout/fetch/orb/tentative/known-mime-type.sub.any.js.ini
index b05b6f9e2bd..cae3ce43d64 100644
--- a/tests/wpt/meta-legacy-layout/fetch/orb/tentative/known-mime-type.sub.any.js.ini
+++ b/tests/wpt/meta-legacy-layout/fetch/orb/tentative/known-mime-type.sub.any.js.ini
@@ -27,101 +27,26 @@
[ORB should block opaque text/plain: fetch(..., {mode: "no-cors"})]
expected: FAIL
- [ORB should block opaque text/plain: <audio src=...>]
- expected: TIMEOUT
-
- [ORB should block opaque text/plain: <video src=...>]
- expected: NOTRUN
-
[ORB should block opaque text/plain: <script src=...>]
- expected: NOTRUN
+ expected: FAIL
[ORB should block opaque application/json (non-empty): fetch(..., {mode: "no-cors"})]
- expected: NOTRUN
-
- [ORB should block opaque application/json (non-empty): <img src=...>]
- expected: NOTRUN
-
- [ORB should block opaque application/json (non-empty): <audio src=...>]
- expected: NOTRUN
-
- [ORB should block opaque application/json (non-empty): <video src=...>]
- expected: NOTRUN
+ expected: FAIL
[ORB should block opaque application/json (non-empty): <script src=...>]
- expected: NOTRUN
+ expected: FAIL
[ORB should block opaque application/json (empty): fetch(..., {mode: "no-cors"})]
- expected: NOTRUN
-
- [ORB should block opaque application/json (empty): <img src=...>]
- expected: NOTRUN
-
- [ORB should block opaque application/json (empty): <audio src=...>]
- expected: NOTRUN
-
- [ORB should block opaque application/json (empty): <video src=...>]
- expected: NOTRUN
+ expected: FAIL
[ORB should block opaque application/json (empty): <script src=...>]
- expected: NOTRUN
+ expected: FAIL
[ORB should block opaque application/json which contains non ascii characters: fetch(..., {mode: "no-cors"})]
- expected: NOTRUN
-
- [ORB should block opaque application/json which contains non ascii characters: <img src=...>]
- expected: NOTRUN
-
- [ORB should block opaque application/json which contains non ascii characters: <audio src=...>]
- expected: NOTRUN
-
- [ORB should block opaque application/json which contains non ascii characters: <video src=...>]
- expected: NOTRUN
+ expected: FAIL
[ORB should block opaque application/json which contains non ascii characters: <script src=...>]
- expected: NOTRUN
-
- [ORB shouldn't block opaque image/png: fetch(..., {mode: "no-cors"})]
- expected: NOTRUN
-
- [ORB shouldn't block opaque image/png: <img src=...>]
- expected: NOTRUN
-
- [ORB shouldn't block opaque text/javascript: fetch(..., {mode: "no-cors"})]
- expected: NOTRUN
-
- [ORB shouldn't block opaque text/javascript: <script src=...>]
- expected: NOTRUN
-
- [ORB shouldn't block opaque text/javascript (utf16 encoded with BOM): fetch(..., {mode: "no-cors"})]
- expected: NOTRUN
-
- [ORB shouldn't block opaque text/javascript (utf16 encoded with BOM): <script src=...>]
- expected: NOTRUN
-
- [ORB shouldn't block opaque text/javascript (utf16 encoded without BOM but charset is provided in content-type): fetch(..., {mode: "no-cors"})]
- expected: NOTRUN
-
- [ORB shouldn't block opaque text/javascript (utf16 encoded without BOM but charset is provided in content-type): <script src=...>]
- expected: NOTRUN
-
- [ORB shouldn't block opaque text/javascript (iso-8559-1 encoded): fetch(..., {mode: "no-cors"})]
- expected: NOTRUN
-
- [ORB shouldn't block opaque text/javascript (iso-8559-1 encoded): <script src=...>]
- expected: NOTRUN
-
- [ORB shouldn't block text/javascript with valid asm.js: fetch(..., {mode: "no-cors"})]
- expected: NOTRUN
-
- [ORB shouldn't block text/javascript with valid asm.js: <script src=...>]
- expected: NOTRUN
-
- [ORB shouldn't block text/javascript with invalid asm.js: fetch(..., {mode: "no-cors"})]
- expected: NOTRUN
-
- [ORB shouldn't block text/javascript with invalid asm.js: <script src=...>]
- expected: NOTRUN
+ expected: FAIL
[known-mime-type.sub.any.worker.html]
diff --git a/tests/wpt/meta-legacy-layout/fetch/orb/tentative/nosniff.sub.any.js.ini b/tests/wpt/meta-legacy-layout/fetch/orb/tentative/nosniff.sub.any.js.ini
index d922a3ef99a..a38b187f919 100644
--- a/tests/wpt/meta-legacy-layout/fetch/orb/tentative/nosniff.sub.any.js.ini
+++ b/tests/wpt/meta-legacy-layout/fetch/orb/tentative/nosniff.sub.any.js.ini
@@ -1,5 +1,5 @@
[nosniff.sub.any.html]
- expected: TIMEOUT
+ expected: ERROR
[ORB should block opaque text/plain with nosniff]
expected: FAIL
@@ -12,50 +12,20 @@
[ORB should block opaque text/plain with nosniff: fetch(..., {mode: "no-cors"})]
expected: FAIL
- [ORB should block opaque text/plain with nosniff: <audio src=...>]
- expected: TIMEOUT
-
- [ORB should block opaque text/plain with nosniff: <video src=...>]
- expected: NOTRUN
-
[ORB should block opaque text/plain with nosniff: <script src=...>]
- expected: NOTRUN
+ expected: FAIL
[ORB should block opaque-response-blocklisted MIME type with nosniff: fetch(..., {mode: "no-cors"})]
- expected: NOTRUN
-
- [ORB should block opaque-response-blocklisted MIME type with nosniff: <img src=...>]
- expected: NOTRUN
-
- [ORB should block opaque-response-blocklisted MIME type with nosniff: <audio src=...>]
- expected: NOTRUN
-
- [ORB should block opaque-response-blocklisted MIME type with nosniff: <video src=...>]
- expected: NOTRUN
+ expected: FAIL
[ORB should block opaque-response-blocklisted MIME type with nosniff: <script src=...>]
- expected: NOTRUN
+ expected: FAIL
[ORB should block opaque response with empty Content-Type and nosniff: fetch(..., {mode: "no-cors"})]
- expected: NOTRUN
-
- [ORB should block opaque response with empty Content-Type and nosniff: <img src=...>]
- expected: NOTRUN
-
- [ORB should block opaque response with empty Content-Type and nosniff: <audio src=...>]
- expected: NOTRUN
-
- [ORB should block opaque response with empty Content-Type and nosniff: <video src=...>]
- expected: NOTRUN
+ expected: FAIL
[ORB should block opaque response with empty Content-Type and nosniff: <script src=...>]
- expected: NOTRUN
-
- [ORB shouldn't block opaque image with empty Content-Type and nosniff: fetch(..., {mode: "no-cors"})]
- expected: NOTRUN
-
- [ORB shouldn't block opaque image with empty Content-Type and nosniff: <img src=...>]
- expected: NOTRUN
+ expected: FAIL
[nosniff.sub.any.worker.html]
diff --git a/tests/wpt/meta-legacy-layout/fetch/orb/tentative/status.sub.any.js.ini b/tests/wpt/meta-legacy-layout/fetch/orb/tentative/status.sub.any.js.ini
index 332ee884317..c0940d7ac48 100644
--- a/tests/wpt/meta-legacy-layout/fetch/orb/tentative/status.sub.any.js.ini
+++ b/tests/wpt/meta-legacy-layout/fetch/orb/tentative/status.sub.any.js.ini
@@ -13,7 +13,7 @@
[status.sub.any.html]
- expected: TIMEOUT
+ expected: ERROR
[ORB should block opaque-response-blocklisted MIME type with status 206]
expected: FAIL
@@ -23,26 +23,8 @@
[ORB should block opaque-response-blocklisted MIME type with status 206: fetch(..., {mode: "no-cors"})]
expected: FAIL
- [ORB should block opaque-response-blocklisted MIME type with status 206: <audio src=...>]
- expected: TIMEOUT
-
- [ORB should block opaque-response-blocklisted MIME type with status 206: <video src=...>]
- expected: NOTRUN
-
[ORB should block opaque-response-blocklisted MIME type with status 206: <script src=...>]
- expected: NOTRUN
+ expected: FAIL
[ORB should block opaque response with non-ok status: fetch(..., {mode: "no-cors"})]
- expected: NOTRUN
-
- [ORB should block opaque response with non-ok status: <img src=...>]
- expected: NOTRUN
-
- [ORB should block opaque response with non-ok status: <audio src=...>]
- expected: NOTRUN
-
- [ORB should block opaque response with non-ok status: <video src=...>]
- expected: NOTRUN
-
- [ORB should block opaque response with non-ok status: <script src=...>]
- expected: NOTRUN
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/fetch/range/non-matching-range-response.html.ini b/tests/wpt/meta-legacy-layout/fetch/range/non-matching-range-response.html.ini
new file mode 100644
index 00000000000..0e0aa9e5c23
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/fetch/range/non-matching-range-response.html.ini
@@ -0,0 +1,3 @@
+[non-matching-range-response.html]
+ [Range response out of range of request should succeed]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-cross-origin.sub.html.ini b/tests/wpt/meta-legacy-layout/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-cross-origin.sub.html.ini
deleted file mode 100644
index 283c44174a7..00000000000
--- a/tests/wpt/meta-legacy-layout/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-cross-origin.sub.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[first-party-to-third-party-cross-partition-cross-origin.sub.html]
- expected: TIMEOUT
- [postMessage: First-Party to Third-Party, Cross-Partition, Cross-Origin]
- expected: TIMEOUT
diff --git a/tests/wpt/meta-legacy-layout/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-same-origin.sub.html.ini b/tests/wpt/meta-legacy-layout/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-same-origin.sub.html.ini
deleted file mode 100644
index 762a134ed26..00000000000
--- a/tests/wpt/meta-legacy-layout/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-same-origin.sub.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[first-party-to-third-party-cross-partition-same-origin.sub.html]
- expected: TIMEOUT
- [postMessage: First-Party to Third-Party, Cross-Partition, Same-Origin]
- expected: TIMEOUT
diff --git a/tests/wpt/meta-legacy-layout/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html.ini b/tests/wpt/meta-legacy-layout/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html.ini
deleted file mode 100644
index e211e6be83a..00000000000
--- a/tests/wpt/meta-legacy-layout/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[third-party-to-third-party-cross-partition-cross-origin.sub.html]
- expected: TIMEOUT
- [postMessage: Third-Party to Third-Party, Cross-Partition, Cross-Origin]
- expected: TIMEOUT
diff --git a/tests/wpt/meta-legacy-layout/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html.ini b/tests/wpt/meta-legacy-layout/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html.ini
deleted file mode 100644
index 488c6bd15d4..00000000000
--- a/tests/wpt/meta-legacy-layout/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[third-party-to-third-party-cross-partition-same-origin.sub.html]
- expected: TIMEOUT
- [postMessage: Third-Party to Third-Party, Cross-Partition, Same-Origin]
- expected: TIMEOUT
diff --git a/tests/wpt/meta-legacy-layout/html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html.ini b/tests/wpt/meta-legacy-layout/html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html.ini
deleted file mode 100644
index 30607f12f29..00000000000
--- a/tests/wpt/meta-legacy-layout/html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[third-party-to-third-party-same-partition.sub.html]
- expected: TIMEOUT
- [postMessage: Third-Party to Third-Party, Same-Partition]
- expected: TIMEOUT
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/element/manual/imagebitmap/createImageBitmap-colorSpaceConversion.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/element/manual/imagebitmap/createImageBitmap-colorSpaceConversion.html.ini
index a509d94b8c6..b44bcf4550f 100644
--- a/tests/wpt/meta-legacy-layout/html/canvas/element/manual/imagebitmap/createImageBitmap-colorSpaceConversion.html.ini
+++ b/tests/wpt/meta-legacy-layout/html/canvas/element/manual/imagebitmap/createImageBitmap-colorSpaceConversion.html.ini
@@ -1,4 +1,5 @@
[createImageBitmap-colorSpaceConversion.html]
+ expected: ERROR
[createImageBitmap from a bitmap HTMLImageElement, and drawImage on the created ImageBitmap with colorSpaceConversion: none]
expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/element/manual/imagebitmap/createImageBitmap-serializable.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/element/manual/imagebitmap/createImageBitmap-serializable.html.ini
index 822d86ef159..ea398e580ef 100644
--- a/tests/wpt/meta-legacy-layout/html/canvas/element/manual/imagebitmap/createImageBitmap-serializable.html.ini
+++ b/tests/wpt/meta-legacy-layout/html/canvas/element/manual/imagebitmap/createImageBitmap-serializable.html.ini
@@ -1,5 +1,5 @@
[createImageBitmap-serializable.html]
- expected: TIMEOUT
+ expected: ERROR
[Serialize ImageBitmap created from a vector SVGImageElement]
expected: NOTRUN
@@ -35,4 +35,3 @@
[Serialize ImageBitmap created from a bitmap SVGImageElement]
expected: TIMEOUT
-
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.caret-position-edge-cases.tentative.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.caret-position-edge-cases.tentative.html.ini
new file mode 100644
index 00000000000..751ca58fa7e
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.caret-position-edge-cases.tentative.html.ini
@@ -0,0 +1,3 @@
+[2d.text.measure.caret-position-edge-cases.tentative.html]
+ [Test the edge cases for caretPositionFromPoint, where the point is at the edge of glyph and at the midpoint.]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.caret-position-edges.tentative.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.caret-position-edges.tentative.html.ini
new file mode 100644
index 00000000000..a8f84833ae9
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.caret-position-edges.tentative.html.ini
@@ -0,0 +1,30 @@
+[2d.text.measure.caret-position-edges.tentative.html]
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align left.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align left.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align center.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align center.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align right.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align right.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align start.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align start.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align end.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align end.]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.caret-position.tentative.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.caret-position.tentative.html.ini
new file mode 100644
index 00000000000..2b7dea3b3b2
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.caret-position.tentative.html.ini
@@ -0,0 +1,120 @@
+[2d.text.measure.caret-position.tentative.html]
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 10px letter spacing and directional-override.]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html.ini
new file mode 100644
index 00000000000..26263231bef
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html.ini
@@ -0,0 +1,2 @@
+[2d.text.measure.getActualBoundingBox-exceptions.tentative.html]
+ expected: ERROR
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html.ini
new file mode 100644
index 00000000000..174591cf89f
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html.ini
@@ -0,0 +1,12 @@
+[2d.text.measure.getActualBoundingBox-full-text.tentative.html]
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and no-directional-override]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and no-directional-override]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and directional-override]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and directional-override]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.getActualBoundingBox.tentative.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.getActualBoundingBox.tentative.html.ini
new file mode 100644
index 00000000000..fbde9c92f4b
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.getActualBoundingBox.tentative.html.ini
@@ -0,0 +1,18 @@
+[2d.text.measure.getActualBoundingBox.tentative.html]
+ [Test TextMetrics::getActualBoundingBox(), with text align left , and 0px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align center , and 0px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align right , and 0px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align left , and 10px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align center , and 10px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align right , and 10px letter spacing.]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.selection-rects.tentative.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.selection-rects.tentative.html.ini
index bd83dffdce1..26358feced0 100644
--- a/tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.selection-rects.tentative.html.ini
+++ b/tests/wpt/meta-legacy-layout/html/canvas/element/text/2d.text.measure.selection-rects.tentative.html.ini
@@ -1,3 +1,75 @@
[2d.text.measure.selection-rects.tentative.html]
[Check that TextMetrics::getSelectionRects() matches its DOM equivalent.]
expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 10px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 10px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 10px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 10px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 10px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 10px letter spacing, and directional-override.]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillText.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillText.html.ini
deleted file mode 100644
index 0a8b4be90d6..00000000000
--- a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillText.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[2d.gradient.interpolate.zerosize.fillText.html]
- [OffscreenCanvas test: 2d.gradient.interpolate.zerosize.fillText]
- expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillText.worker.js.ini b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillText.worker.js.ini
deleted file mode 100644
index 0a6c14e61c4..00000000000
--- a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillText.worker.js.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[2d.gradient.interpolate.zerosize.fillText.worker.html]
- [2d]
- expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/reset/2d.reset.render.text.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/reset/2d.reset.render.text.html.ini
new file mode 100644
index 00000000000..bab41d308bf
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/reset/2d.reset.render.text.html.ini
@@ -0,0 +1,2 @@
+[2d.reset.render.text.html]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.html.ini
new file mode 100644
index 00000000000..751ca58fa7e
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.html.ini
@@ -0,0 +1,3 @@
+[2d.text.measure.caret-position-edge-cases.tentative.html]
+ [Test the edge cases for caretPositionFromPoint, where the point is at the edge of glyph and at the midpoint.]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.worker.js.ini b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.worker.js.ini
new file mode 100644
index 00000000000..5d4cad8801f
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.worker.js.ini
@@ -0,0 +1,3 @@
+[2d.text.measure.caret-position-edge-cases.tentative.worker.html]
+ [Test the edge cases for caretPositionFromPoint, where the point is at the edge of glyph and at the midpoint.]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.html.ini
new file mode 100644
index 00000000000..a8f84833ae9
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.html.ini
@@ -0,0 +1,30 @@
+[2d.text.measure.caret-position-edges.tentative.html]
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align left.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align left.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align center.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align center.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align right.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align right.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align start.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align start.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align end.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align end.]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.worker.js.ini b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.worker.js.ini
new file mode 100644
index 00000000000..08ebd3f82f7
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.worker.js.ini
@@ -0,0 +1,30 @@
+[2d.text.measure.caret-position-edges.tentative.worker.html]
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align left.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align left.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align center.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align center.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align right.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align right.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align start.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align start.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align end.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align end.]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.caret-position.tentative.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.caret-position.tentative.html.ini
new file mode 100644
index 00000000000..2b7dea3b3b2
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.caret-position.tentative.html.ini
@@ -0,0 +1,120 @@
+[2d.text.measure.caret-position.tentative.html]
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 10px letter spacing and directional-override.]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html.ini
new file mode 100644
index 00000000000..26263231bef
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html.ini
@@ -0,0 +1,2 @@
+[2d.text.measure.getActualBoundingBox-exceptions.tentative.html]
+ expected: ERROR
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.worker.js.ini b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.worker.js.ini
new file mode 100644
index 00000000000..615275f70fe
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.worker.js.ini
@@ -0,0 +1,2 @@
+[2d.text.measure.getActualBoundingBox-exceptions.tentative.worker.html]
+ expected: TIMEOUT
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html.ini
new file mode 100644
index 00000000000..174591cf89f
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html.ini
@@ -0,0 +1,12 @@
+[2d.text.measure.getActualBoundingBox-full-text.tentative.html]
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and no-directional-override]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and no-directional-override]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and directional-override]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and directional-override]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.worker.js.ini b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.worker.js.ini
new file mode 100644
index 00000000000..2f1f2db1ce6
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.worker.js.ini
@@ -0,0 +1,12 @@
+[2d.text.measure.getActualBoundingBox-full-text.tentative.worker.html]
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and no-directional-override]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and no-directional-override]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and directional-override]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and directional-override]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.html.ini
new file mode 100644
index 00000000000..fbde9c92f4b
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.html.ini
@@ -0,0 +1,18 @@
+[2d.text.measure.getActualBoundingBox.tentative.html]
+ [Test TextMetrics::getActualBoundingBox(), with text align left , and 0px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align center , and 0px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align right , and 0px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align left , and 10px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align center , and 10px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align right , and 10px letter spacing.]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.worker.js.ini b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.worker.js.ini
new file mode 100644
index 00000000000..f3cc13a0b5e
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.worker.js.ini
@@ -0,0 +1,18 @@
+[2d.text.measure.getActualBoundingBox.tentative.worker.html]
+ [Test TextMetrics::getActualBoundingBox(), with text align left , and 0px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align center , and 0px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align right , and 0px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align left , and 10px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align center , and 10px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align right , and 10px letter spacing.]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.selection-rects.tentative.html.ini b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.selection-rects.tentative.html.ini
index bd83dffdce1..26358feced0 100644
--- a/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.selection-rects.tentative.html.ini
+++ b/tests/wpt/meta-legacy-layout/html/canvas/offscreen/text/2d.text.measure.selection-rects.tentative.html.ini
@@ -1,3 +1,75 @@
[2d.text.measure.selection-rects.tentative.html]
[Check that TextMetrics::getSelectionRects() matches its DOM equivalent.]
expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 10px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 10px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 10px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 10px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 10px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 10px letter spacing, and directional-override.]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/dom/elements/global-attributes/cdata-dir_auto.html.ini b/tests/wpt/meta-legacy-layout/html/dom/elements/global-attributes/cdata-dir_auto.html.ini
new file mode 100644
index 00000000000..e21e72894e5
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/html/dom/elements/global-attributes/cdata-dir_auto.html.ini
@@ -0,0 +1,12 @@
+[cdata-dir_auto.html]
+ [Content of CDATA is ignored for dir=auto in html document]
+ expected: FAIL
+
+ [Text in CDATASection is considered when determining auto directionality]
+ expected: FAIL
+
+ [Directionality is updated when removing CDATASection]
+ expected: FAIL
+
+ [Directionality is updated when changing text of CDATASection]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/dom/elements/global-attributes/dir-assorted.window.js.ini b/tests/wpt/meta-legacy-layout/html/dom/elements/global-attributes/dir-assorted.window.js.ini
index 0daeceb99a1..1e6939b0478 100644
--- a/tests/wpt/meta-legacy-layout/html/dom/elements/global-attributes/dir-assorted.window.js.ini
+++ b/tests/wpt/meta-legacy-layout/html/dom/elements/global-attributes/dir-assorted.window.js.ini
@@ -118,3 +118,6 @@
[non-html textarea element text contents influence dir=auto]
expected: FAIL
+
+ [text changes apply to dir=auto on further ancestor after removing dir=ltr from closer ancestor]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/dom/elements/global-attributes/dir-auto-dynamic-changes.window.js.ini b/tests/wpt/meta-legacy-layout/html/dom/elements/global-attributes/dir-auto-dynamic-changes.window.js.ini
index 8becf694cfa..7395ce6f2ed 100644
--- a/tests/wpt/meta-legacy-layout/html/dom/elements/global-attributes/dir-auto-dynamic-changes.window.js.ini
+++ b/tests/wpt/meta-legacy-layout/html/dom/elements/global-attributes/dir-auto-dynamic-changes.window.js.ini
@@ -22,3 +22,27 @@
[slotted non-HTML elements after dynamically assigning dir=auto, and dir attribute ignored on non-HTML elements]
expected: FAIL
+
+ [dir=auto ancestor considers text in subtree after removing dir=ltr from it]
+ expected: FAIL
+
+ [Slotted content affects multiple dir=auto slots]
+ expected: FAIL
+
+ [Removing slotted content resets direction on dir=auto slot]
+ expected: FAIL
+
+ [Removing child of slotted content changes direction on dir=auto slot]
+ expected: FAIL
+
+ [Child directionality gets updated when dir=auto is set on parent]
+ expected: FAIL
+
+ [dir=auto slot is updated by text in value of input element children]
+ expected: FAIL
+
+ [dir=auto slot is updated if input stops being auto-directionality form-associated]
+ expected: FAIL
+
+ [slot provides updated directionality from host to a dir=auto container]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/dom/elements/global-attributes/dir-slots-directionality.html.ini b/tests/wpt/meta-legacy-layout/html/dom/elements/global-attributes/dir-slots-directionality.html.ini
index 2ff8bf31e7e..cd0a57e465c 100644
--- a/tests/wpt/meta-legacy-layout/html/dom/elements/global-attributes/dir-slots-directionality.html.ini
+++ b/tests/wpt/meta-legacy-layout/html/dom/elements/global-attributes/dir-slots-directionality.html.ini
@@ -25,3 +25,12 @@
[slot with dir attribute is skipped by dir=auto]
expected: FAIL
+
+ [dir=auto slot ignores dir attribute of assigned nodes]
+ expected: FAIL
+
+ [dir=auto slot considers text in bdi assigned nodes]
+ expected: FAIL
+
+ [dir=auto slot considers text in value of input element children]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/dom/idlharness.https.html.ini b/tests/wpt/meta-legacy-layout/html/dom/idlharness.https.html.ini
index 6cff25386a5..cdbd11bee52 100644
--- a/tests/wpt/meta-legacy-layout/html/dom/idlharness.https.html.ini
+++ b/tests/wpt/meta-legacy-layout/html/dom/idlharness.https.html.ini
@@ -1580,36 +1580,6 @@
[SVGElement interface: attribute onbeforetoggle]
expected: FAIL
- [VisibilityStateEntry interface: existence and properties of interface object]
- expected: FAIL
-
- [VisibilityStateEntry interface object length]
- expected: FAIL
-
- [VisibilityStateEntry interface object name]
- expected: FAIL
-
- [VisibilityStateEntry interface: existence and properties of interface prototype object]
- expected: FAIL
-
- [VisibilityStateEntry interface: existence and properties of interface prototype object's "constructor" property]
- expected: FAIL
-
- [VisibilityStateEntry interface: existence and properties of interface prototype object's @@unscopables property]
- expected: FAIL
-
- [VisibilityStateEntry interface: attribute name]
- expected: FAIL
-
- [VisibilityStateEntry interface: attribute entryType]
- expected: FAIL
-
- [VisibilityStateEntry interface: attribute startTime]
- expected: FAIL
-
- [VisibilityStateEntry interface: attribute duration]
- expected: FAIL
-
[Navigation interface: existence and properties of interface object]
expected: FAIL
@@ -2608,12 +2578,6 @@
[Window interface: calling structuredClone(any, optional StructuredSerializeOptions) on window with too few arguments must throw TypeError]
expected: FAIL
- [Document interface: attribute hidden]
- expected: FAIL
-
- [Document interface: attribute visibilityState]
- expected: FAIL
-
[Document interface: attribute onvisibilitychange]
expected: FAIL
@@ -2623,12 +2587,6 @@
[Document interface: attribute oncontextrestored]
expected: FAIL
- [Document interface: iframe.contentDocument must inherit property "hidden" with the proper type]
- expected: FAIL
-
- [Document interface: iframe.contentDocument must inherit property "visibilityState" with the proper type]
- expected: FAIL
-
[Document interface: iframe.contentDocument must inherit property "onvisibilitychange" with the proper type]
expected: FAIL
@@ -2638,12 +2596,6 @@
[Document interface: iframe.contentDocument must inherit property "oncontextrestored" with the proper type]
expected: FAIL
- [Document interface: new Document() must inherit property "hidden" with the proper type]
- expected: FAIL
-
- [Document interface: new Document() must inherit property "visibilityState" with the proper type]
- expected: FAIL
-
[Document interface: new Document() must inherit property "onvisibilitychange" with the proper type]
expected: FAIL
@@ -2653,12 +2605,6 @@
[Document interface: new Document() must inherit property "oncontextrestored" with the proper type]
expected: FAIL
- [Document interface: documentWithHandlers must inherit property "hidden" with the proper type]
- expected: FAIL
-
- [Document interface: documentWithHandlers must inherit property "visibilityState" with the proper type]
- expected: FAIL
-
[Document interface: documentWithHandlers must inherit property "onvisibilitychange" with the proper type]
expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/audio_loop_base.html.ini b/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/audio_loop_base.html.ini
deleted file mode 100644
index c6977a66f6d..00000000000
--- a/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/audio_loop_base.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[audio_loop_base.html]
- expected: TIMEOUT
- [Check if audio.loop is set to true that expecting the seeking event is fired more than once]
- expected: NOTRUN
-
diff --git a/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/audio_loop_seek_to_eos.html.ini b/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/audio_loop_seek_to_eos.html.ini
deleted file mode 100644
index 235627d1539..00000000000
--- a/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/audio_loop_seek_to_eos.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[audio_loop_seek_to_eos.html]
- [seeking to the end of looping audio]
- expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/playing-the-media-resource/loop-from-ended.tentative.html.ini b/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/playing-the-media-resource/loop-from-ended.tentative.html.ini
index 6d9188c1e52..6897533d946 100644
--- a/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/playing-the-media-resource/loop-from-ended.tentative.html.ini
+++ b/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/playing-the-media-resource/loop-from-ended.tentative.html.ini
@@ -1,7 +1,7 @@
[loop-from-ended.tentative.html]
+ expected: TIMEOUT
[Test looping edge case to verify http://crbug.com/364442.]
expected: FAIL
[play() with loop set to true after playback ended]
- expected: FAIL
-
+ expected: TIMEOUT
diff --git a/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/track/track-element/no-cuechange-before-play.html.ini b/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/track/track-element/no-cuechange-before-play.html.ini
index 4caf5931a11..246e55deb7e 100644
--- a/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/track/track-element/no-cuechange-before-play.html.ini
+++ b/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/track/track-element/no-cuechange-before-play.html.ini
@@ -1,3 +1,4 @@
[no-cuechange-before-play.html]
+ expected: TIMEOUT
[Ensure that the 'cuechange' event is not fired before video playback has begun.]
- expected: FAIL
+ expected: TIMEOUT
diff --git a/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/track/track-element/track-cue-mutable-fragment.html.ini b/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/track/track-element/track-cue-mutable-fragment.html.ini
index c1216306fcf..4044b3cd259 100644
--- a/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/track/track-element/track-cue-mutable-fragment.html.ini
+++ b/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/track/track-element/track-cue-mutable-fragment.html.ini
@@ -1,4 +1,4 @@
[track-cue-mutable-fragment.html]
+ expected: TIMEOUT
[Cue fragment is mutable]
- expected: FAIL
-
+ expected: TIMEOUT
diff --git a/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/track/track-element/track-mode-not-changed-by-new-track.html.ini b/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/track/track-element/track-mode-not-changed-by-new-track.html.ini
index d0fc025a457..305b9bd7896 100644
--- a/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/track/track-element/track-mode-not-changed-by-new-track.html.ini
+++ b/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/media-elements/track/track-element/track-mode-not-changed-by-new-track.html.ini
@@ -1,4 +1,4 @@
[track-mode-not-changed-by-new-track.html]
+ expected: TIMEOUT
[A track appended after the initial track configuration does not change other tracks]
- expected: FAIL
-
+ expected: TIMEOUT
diff --git a/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-nav-form-submit.html.ini b/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-nav-form-submit.html.ini
new file mode 100644
index 00000000000..721952ed48a
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-nav-form-submit.html.ini
@@ -0,0 +1,3 @@
+[iframe-loading-lazy-nav-form-submit.html]
+ [Navigating iframe loading='lazy' before it is loaded: form submit]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-nav-location-assign.html.ini b/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-nav-location-assign.html.ini
deleted file mode 100644
index 68a30137729..00000000000
--- a/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-nav-location-assign.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[iframe-loading-lazy-nav-location-assign.html]
- [Navigating iframe loading='lazy' before it is loaded: location.assign]
- expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-3.html.ini b/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-3.html.ini
index eacbe5794ea..fe55ddae3f1 100644
--- a/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-3.html.ini
+++ b/tests/wpt/meta-legacy-layout/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-3.html.ini
@@ -1,5 +1,4 @@
[iframe_sandbox_popups_escaping-3.html]
type: testharness
- expected: TIMEOUT
[Check that popups from a sandboxed iframe escape the sandbox if\n allow-popups-to-escape-sandbox is used]
- expected: TIMEOUT
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/semantics/invokers/idlharness.tentative.html.ini b/tests/wpt/meta-legacy-layout/html/semantics/invokers/idlharness.tentative.html.ini
index f9d4b78a7c6..cd96793eab1 100644
--- a/tests/wpt/meta-legacy-layout/html/semantics/invokers/idlharness.tentative.html.ini
+++ b/tests/wpt/meta-legacy-layout/html/semantics/invokers/idlharness.tentative.html.ini
@@ -34,3 +34,39 @@
[InvokeEvent interface: new InvokeEvent("invoke") must inherit property "action" with the proper type]
expected: FAIL
+
+ [CommandEvent interface: existence and properties of interface object]
+ expected: FAIL
+
+ [CommandEvent interface object length]
+ expected: FAIL
+
+ [CommandEvent interface object name]
+ expected: FAIL
+
+ [CommandEvent interface: existence and properties of interface prototype object]
+ expected: FAIL
+
+ [CommandEvent interface: existence and properties of interface prototype object's "constructor" property]
+ expected: FAIL
+
+ [CommandEvent interface: existence and properties of interface prototype object's @@unscopables property]
+ expected: FAIL
+
+ [CommandEvent interface: attribute invoker]
+ expected: FAIL
+
+ [CommandEvent interface: attribute command]
+ expected: FAIL
+
+ [CommandEvent must be primary interface of new CommandEvent("invoke")]
+ expected: FAIL
+
+ [Stringification of new CommandEvent("invoke")]
+ expected: FAIL
+
+ [CommandEvent interface: new CommandEvent("invoke") must inherit property "invoker" with the proper type]
+ expected: FAIL
+
+ [CommandEvent interface: new CommandEvent("invoke") must inherit property "command" with the proper type]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/html/semantics/invokers/invokeelement-interface.tentative.html.ini b/tests/wpt/meta-legacy-layout/html/semantics/invokers/invokeelement-interface.tentative.html.ini
index 587bdaf84d3..d04574db48e 100644
--- a/tests/wpt/meta-legacy-layout/html/semantics/invokers/invokeelement-interface.tentative.html.ini
+++ b/tests/wpt/meta-legacy-layout/html/semantics/invokers/invokeelement-interface.tentative.html.ini
@@ -43,3 +43,33 @@
[invokeAction reflects '' when attribute set to [\]]
expected: FAIL
+
+ [commandForElement reflects invokee HTML element]
+ expected: FAIL
+
+ [commandForElement reflects set value]
+ expected: FAIL
+
+ [commandForElement reflects set value across shadow root into light dom]
+ expected: FAIL
+
+ [commandForElement does not reflect set value inside shadowroot]
+ expected: FAIL
+
+ [commandForElement throws error on assignment of non Element]
+ expected: FAIL
+
+ [command reflects '' when attribute empty, setAttribute version]
+ expected: FAIL
+
+ [command reflects same casing]
+ expected: FAIL
+
+ [command reflects tostring value]
+ expected: FAIL
+
+ [command reflects '' when attribute set to [\]]
+ expected: FAIL
+
+ [command reflects tostring value 2]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/page-visibility/iframe-session-history.html.ini b/tests/wpt/meta-legacy-layout/page-visibility/iframe-session-history.html.ini
new file mode 100644
index 00000000000..64437c7c804
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/page-visibility/iframe-session-history.html.ini
@@ -0,0 +1,3 @@
+[iframe-session-history.html]
+ [pagehide should be called before visibilitychange, and visibilityState should be set to hidden at the right time]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/page-visibility/iframe-unload.html.ini b/tests/wpt/meta-legacy-layout/page-visibility/iframe-unload.html.ini
new file mode 100644
index 00000000000..4df96d83d37
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/page-visibility/iframe-unload.html.ini
@@ -0,0 +1,4 @@
+[iframe-unload.html]
+ expected: TIMEOUT
+ [visibilitychange fires on unload with iframes]
+ expected: TIMEOUT
diff --git a/tests/wpt/meta-legacy-layout/page-visibility/onvisibilitychange.html.ini b/tests/wpt/meta-legacy-layout/page-visibility/onvisibilitychange.html.ini
new file mode 100644
index 00000000000..4998c11da7b
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/page-visibility/onvisibilitychange.html.ini
@@ -0,0 +1,4 @@
+[onvisibilitychange.html]
+ expected: TIMEOUT
+ [onvisibilitychange attribute is a proper event handler]
+ expected: NOTRUN
diff --git a/tests/wpt/meta-legacy-layout/page-visibility/test_child_document.html.ini b/tests/wpt/meta-legacy-layout/page-visibility/test_child_document.html.ini
new file mode 100644
index 00000000000..9e77ac067f4
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/page-visibility/test_child_document.html.ini
@@ -0,0 +1,9 @@
+[test_child_document.html]
+ [document.visibilityState for frame with no style attribute == visible]
+ expected: FAIL
+
+ [document.visibilityState for frame with 'display:none' style == visible]
+ expected: FAIL
+
+ [document.visibilityState for frame with 'visibility:hidden' style == visible]
+ expected: FAIL
diff --git a/tests/wpt/meta-legacy-layout/page-visibility/unload-bubbles.html.ini b/tests/wpt/meta-legacy-layout/page-visibility/unload-bubbles.html.ini
new file mode 100644
index 00000000000..c4af1f5f836
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/page-visibility/unload-bubbles.html.ini
@@ -0,0 +1,4 @@
+[unload-bubbles.html]
+ expected: TIMEOUT
+ [visibilitychange event bubbles when fired on unload]
+ expected: TIMEOUT
diff --git a/tests/wpt/meta-legacy-layout/page-visibility/unload.html.ini b/tests/wpt/meta-legacy-layout/page-visibility/unload.html.ini
new file mode 100644
index 00000000000..6e9a94351a0
--- /dev/null
+++ b/tests/wpt/meta-legacy-layout/page-visibility/unload.html.ini
@@ -0,0 +1,4 @@
+[unload.html]
+ expected: TIMEOUT
+ [visibilitychange fires on unload]
+ expected: TIMEOUT
diff --git a/tests/wpt/meta-legacy-layout/resource-timing/test_resource_timing.https.html.ini b/tests/wpt/meta-legacy-layout/resource-timing/test_resource_timing.https.html.ini
index 32d149c8ae9..54e3ef35665 100644
--- a/tests/wpt/meta-legacy-layout/resource-timing/test_resource_timing.https.html.ini
+++ b/tests/wpt/meta-legacy-layout/resource-timing/test_resource_timing.https.html.ini
@@ -68,6 +68,3 @@
[PerformanceEntry has correct name, initiatorType, startTime, and duration (link)]
expected: NOTRUN
-
- [PerformanceEntry has correct name, initiatorType, startTime, and duration (img)]
- expected: FAIL
diff --git a/tests/wpt/meta/MANIFEST.json b/tests/wpt/meta/MANIFEST.json
index 9cc453d7319..19cdffd47c9 100644
--- a/tests/wpt/meta/MANIFEST.json
+++ b/tests/wpt/meta/MANIFEST.json
@@ -482,6 +482,13 @@
{}
]
],
+ "firefox-bug-1906768.html": [
+ "0300a90b206f2149414d5b00c31c1a45eacc15bc",
+ [
+ null,
+ {}
+ ]
+ ],
"float-rewind-parallel-flow-1-crash.html": [
"79d90b89b6523ccb64502d1b43b6b0a054148cd1",
[
@@ -5549,6 +5556,13 @@
null,
{}
]
+ ],
+ "input-moveBefore-crash.html": [
+ "a41d06b024ebce951d9b84c9759a29b5754d0e96",
+ [
+ null,
+ {}
+ ]
]
}
},
@@ -6737,6 +6751,15 @@
{}
]
]
+ },
+ "widgets": {
+ "input-checkbox-appearance-none-dynamic-crash.html": [
+ "fb10f6a7fa63a62eee988778b902179ab38e3603",
+ [
+ null,
+ {}
+ ]
+ ]
}
},
"semantics": {
@@ -32072,6 +32095,23 @@
]
]
},
+ "css-contain": {
+ "content-visibility": {
+ "content-visibility-auto-print.html": [
+ "35bc333e87976b47971a303db1eb306ffdac5674",
+ [
+ null,
+ [
+ [
+ "/css/css-contain/content-visibility/content-visibility-auto-print-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ]
+ }
+ },
"css-flexbox": {
"break-nested-float-in-flex-item-001-print.html": [
"2fbf939f6a54c9056b4bd39fe0466f0df25f0bb0",
@@ -33201,6 +33241,45 @@
{}
]
],
+ "page-background-001-print.html": [
+ "e1bc2196940047d8948f819c32d65aedd0e76416",
+ [
+ null,
+ [
+ [
+ "/css/css-page/page-background-001-print-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "page-background-002-print.html": [
+ "240d468ce4438ee29a6afb74aee1539b897b5ea5",
+ [
+ null,
+ [
+ [
+ "/css/css-page/page-background-002-print-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "page-background-003-print.html": [
+ "fbc72ecd7d77bd0a2382149019495736e7be34ce",
+ [
+ null,
+ [
+ [
+ "/css/css-page/page-background-003-print-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
"page-background-image-print.html": [
"633cd4ec6c9027902782f271c2161394cf6cb5e6",
[
@@ -134634,6 +134713,71 @@
{}
]
],
+ "box-decoration-break-clone-005.tentative.html": [
+ "88ca6360f101d88e9d91c145ae2757539ca9e5aa",
+ [
+ null,
+ [
+ [
+ "/css/reference/ref-filled-green-100px-square.xht",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "box-decoration-break-clone-006.html": [
+ "657f9f8442dcea268927b20ea0e9b4853912968f",
+ [
+ null,
+ [
+ [
+ "/css/reference/ref-filled-green-100px-square.xht",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "box-decoration-break-clone-007.html": [
+ "f2855edaff8d729c1aa30f3e2bf503c9fc10c262",
+ [
+ null,
+ [
+ [
+ "/css/reference/ref-filled-green-100px-square.xht",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "box-decoration-break-clone-008.html": [
+ "5f13231883515ef1e1f33906e9243c18d5b14c06",
+ [
+ null,
+ [
+ [
+ "/css/reference/ref-filled-green-100px-square.xht",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "box-decoration-break-clone-009.html": [
+ "1db904332aef57c46d406881ec70adbcf4a6a0ec",
+ [
+ null,
+ [
+ [
+ "/css/css-break/box-decoration-break-clone-009-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
"box-shadow-001.html": [
"d35aeb682dcfcc77c41f8b09642b3481a4b7f39d",
[
@@ -146084,7 +146228,7 @@
]
],
"color-mix-non-srgb-001.html": [
- "7563581a4cf6f8438e1ad9a8a25671c3f052ce79",
+ "550a919d241f805a24704015f3b50ac8a84064f3",
[
null,
[
@@ -156458,6 +156602,19 @@
{}
]
],
+ "content-visibility-auto-svg-image.html": [
+ "46de764072df987da7ba52e1790e9334af68bd20",
+ [
+ null,
+ [
+ [
+ "/css/css-contain/content-visibility/content-visibility-auto-svg-image-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
"content-visibility-canvas.html": [
"fe469489528c45797b8ff68bfc739c39cefdf7b0",
[
@@ -201518,6 +201675,19 @@
},
"clip-path": {
"animations": {
+ "clip-path-animation-cancel.html": [
+ "f92a016662b4e0885f8db76575e07081689b29d6",
+ [
+ null,
+ [
+ [
+ "/css/css-masking/clip-path/animations/clip-path-animation-cancel-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
"clip-path-animation-custom-timing-function-reverse.html": [
"5346a4c22421cafde7691356c67b896f22392f76",
[
@@ -214244,6 +214414,84 @@
{}
]
],
+ "line-clamp-022.tentative.html": [
+ "1ae5d94465aac2ebca3a30f749302610e2c175ed",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/line-clamp-022-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "line-clamp-023.tentative.html": [
+ "0e11ba4d8625bc5693cc114202479fbe99fa9e39",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/line-clamp-023-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "line-clamp-024.tentative.html": [
+ "0efb474a11fb7d48dc05439e448dee9e51c5c260",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/line-clamp-022-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "line-clamp-025.tentative.html": [
+ "b2a2c54f450bee13863bfce292164c48a705a7d4",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/line-clamp-023-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "line-clamp-026.tentative.html": [
+ "329d12d10bd1021a6565b09be013aeefea973fc6",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/line-clamp-026-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "line-clamp-027.tentative.html": [
+ "b2a376b2bd93c577e426d5d6f7a52ccf016b0b75",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/line-clamp-027-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
"line-clamp-auto-001.tentative.html": [
"02d8479736d4d6fd7e26df53611624e7a75d0989",
[
@@ -214452,6 +214700,214 @@
{}
]
],
+ "line-clamp-auto-017.tentative.html": [
+ "376c69a210611977b3eae9ed3900b2538516be98",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/webkit-line-clamp-005-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "line-clamp-auto-018.tentative.html": [
+ "deb1f42431d26c503d5abd6f22ba567d2ecd446f",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/line-clamp-auto-018-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "line-clamp-auto-019.tentative.html": [
+ "82c53dc6bbec03a810dc04538a5bc714cd5802fd",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/line-clamp-auto-019-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "line-clamp-auto-020.tentative.html": [
+ "eb84151ca66c1a42b8ff35690946350450fcec46",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/line-clamp-auto-020-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "line-clamp-auto-021.tentative.html": [
+ "90c33755bee669e770cf2a9a699998ea67c19fc6",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/line-clamp-auto-021-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "line-clamp-auto-022.tentative.html": [
+ "7834c9c3ae0e2d2120f5fbc47f7946908235dedd",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/line-clamp-auto-021-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "line-clamp-auto-023.tentative.html": [
+ "d41d68d84a3f9518a64aae73bb6d8f990d9ee160",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/line-clamp-auto-023-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "line-clamp-auto-024.tentative.html": [
+ "134dade711ad9ff4dcb3cb310e23a5a5e5e492ea",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/line-clamp-auto-023-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "line-clamp-auto-025.tentative.html": [
+ "ee394b2a76d12a276c8809577e92993e2e32e7a8",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/line-clamp-auto-025-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "line-clamp-auto-026.tentative.html": [
+ "f616cbed028a82638dfd84465e134230eb109219",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/line-clamp-auto-026-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "line-clamp-auto-027.tentative.html": [
+ "9ed461d8cfb4c2ee035987148e15b6dcce3cdffa",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/line-clamp-auto-027-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "line-clamp-auto-028.tentative.html": [
+ "e3bcf375bc55fa0d51b36af41a716188be52e8b1",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/line-clamp-auto-028-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "line-clamp-auto-029.tentative.html": [
+ "17a3f1a78c1aa045cb71d37a267c69f312b8e5f8",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/line-clamp-auto-029-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "line-clamp-auto-030.tentative.html": [
+ "2a3a73b3e7121b80a4954bcb623e05a988ea43e2",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/line-clamp-auto-029-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "line-clamp-auto-031.tentative.html": [
+ "a723430c83ed7f0ea8ae8f2966d971c5c56d3cff",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/line-clamp-auto-031-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "line-clamp-auto-032.tentative.html": [
+ "17c6981ae33f115d8b7ab4245f44c6c837b44d1a",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/line-clamp-auto-032-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
"line-clamp-with-abspos-001.tentative.html": [
"79667f23fbdc3d941484c343b2cf0a04ec34363f",
[
@@ -214595,6 +215051,32 @@
{}
]
],
+ "line-clamp-with-abspos-012.tentative.html": [
+ "bed065f38381a9b151cee045b37b45de8f7cd0fb",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/webkit-line-clamp-005-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
+ "line-clamp-with-abspos-013.tentative.html": [
+ "ccea125f7246cf526da15a469261c3e90d4d2609",
+ [
+ null,
+ [
+ [
+ "/css/css-overflow/line-clamp/reference/webkit-line-clamp-005-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
"line-clamp-with-floats-001.tentative.html": [
"98bbdcb9040224c9c9df1a535ffdeb426040a3c7",
[
@@ -220925,6 +221407,19 @@
],
{}
]
+ ],
+ "registered-property-computation-color-004.html": [
+ "66abd1b3c32db1361d6e030e8d1759d1da65f355",
+ [
+ null,
+ [
+ [
+ "/css/reference/ref-filled-green-100px-square-only.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
]
},
"css-pseudo": {
@@ -236754,6 +237249,19 @@
{}
]
],
+ "collapsed-border-remove-row-group.html": [
+ "39b89956c54c3b589e9396c23a7ce87c6028a2cb",
+ [
+ null,
+ [
+ [
+ "/css/css-tables/collapsed-border-remove-row-group-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
"colspan-004.html": [
"cbe7f96ec2c44466e42c619ca8d1db676e6cd6db",
[
@@ -272007,6 +272515,19 @@
{}
]
],
+ "pseudo-element-transform.html": [
+ "1919835e3e043a1b7555882710d575e48c9c288f",
+ [
+ null,
+ [
+ [
+ "/css/css-transitions/pseudo-element-transform-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
"render-blocking": {
"no-transition-from-ua-to-blocking-stylesheet.html": [
"d59118c66f942d1192ad5e311e0c00a1985d4e60",
@@ -337212,6 +337733,19 @@
],
{}
]
+ ],
+ "padding-border-margin-004.html": [
+ "ee8196d8597e66d659e97bb6ac911dff2e0a08c1",
+ [
+ null,
+ [
+ [
+ "/mathml/relations/css-styling/padding-border-margin/padding-border-margin-004-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
]
},
"presentational-hints-001.html": [
@@ -338844,6 +339378,19 @@
},
"selection": {
"caret": {
+ "after-designMode-off.html": [
+ "c5ccd66cc4b77650095929aaaea39038073d653a",
+ [
+ null,
+ [
+ [
+ "/selection/caret/after-designMode-off-ref.html",
+ "=="
+ ]
+ ],
+ {}
+ ]
+ ],
"collapse-pre-linestart-1.html": [
"e8bd262868cb1bdc2734a50a934d3222e6d69fe0",
[
@@ -341103,7 +341650,7 @@
]
],
"non-scaling-stroke-003.html": [
- "147bf814d30f10094b77aeb4eb0fc25396cda00e",
+ "4fec5711d6c46e1da279f3cbd24da0a45a723e08",
[
null,
[
@@ -341283,6 +341830,19 @@
],
{}
]
+ ],
+ "symbol-in-mask.html": [
+ "da1e11f1704468c2ec77476ea1c566299737d9f9",
+ [
+ null,
+ [
+ [
+ "/svg/painting/reftests/green-100x100.svg",
+ "=="
+ ]
+ ],
+ {}
+ ]
]
},
"scripted": {
@@ -347065,7 +347625,7 @@
],
"workflows": {
"docker.yml": [
- "32fb814e6d94c4d71bca85e06512a9b3ce6c7783",
+ "c6cb0ac58ef342491f46cb3527bcf1dc35d0e83d",
[]
],
"documentation.yml": [
@@ -347099,7 +347659,7 @@
[]
],
".taskcluster.yml": [
- "38af7fa10af8396c8b41732b7b617bf408df0446",
+ "85204ee17b9255ebd7dcd08716acd7da59b75d57",
[]
],
".well-known": {
@@ -347191,7 +347751,7 @@
]
},
"CODEOWNERS": [
- "140e0c6545b855cabd9c23b9aa27f6493081c93d",
+ "07bda888b209dcd0d66e4fc87c94a2042771a595",
[]
],
"CODE_OF_CONDUCT.md": [
@@ -372255,101 +372815,19 @@
"7b1860921312cc9d9934aa803e3f639df21423a0",
[]
],
- "fedcm-cross-origin-policy.https.html.sub.headers": [
- "32523a697886f7fb26ab4456e5b14cb19d8c1aae",
- []
- ],
- "fedcm-csp.https.html.sub.headers": [
- "c1e6fd6c4c281f22e7f92506ec72ca39b6293f23",
- []
- ],
- "fedcm-opaque-rp-origin.https.html.headers": [
- "9850d21f3c6fa83cd87c23f6b0f4150b64011918",
- []
- ],
"support": {
"README.md": [
- "a6d33ff6f75c408712f8b1cf6b78b50326d4f3fc",
+ "f58d4cd0ce430ec61840b6d745a8cefe41643a88",
[]
],
"echoing-nester.html": [
"d4f5899da7894749a51039f9d5bf3b8bfd680570",
[]
],
- "fedcm": {
- "disconnect-iframe.html": [
- "f65763932b8c742debdbabbefc310613fe83f0e5",
- []
- ],
- "intercept_service_worker.js": [
- "fd0bb71a0c7ac405a7b1d148d6a08341c44117d7",
- []
- ],
- "pending-userinfo-iframe.html": [
- "da2cd26066abd87eaa04d5ade3845424d49de103",
- []
- ],
- "simple.html": [
- "d62419ce8a0ac12a85f5b8e595a874714b038b44",
- []
- ],
- "userinfo-iframe.html": [
- "64d5cb83a088db7dc8f3a9e7e2a65301eb0b64b1",
- []
- ]
- },
- "fedcm-helper.sub.js": [
- "17ed5ce4468492368c3bf33e8d9462f9c87641e4",
- []
- ],
- "fedcm-helper.sub.js.headers": [
- "cb762eff806849df46dc758ef7b98b63f27f54c9",
- []
- ],
- "fedcm-iframe-level2.html": [
- "7622d988ff2c129003f0e3251bb7a0be698d17d6",
- []
- ],
- "fedcm-iframe.html": [
- "ba79c4cf9e955672d94450143dff091a6691a0e2",
- []
- ],
- "fedcm-mock.js": [
- "271dd9cd944a2770f27b119331dc77d919ba9cf5",
- []
- ],
- "fedcm-mojojs-helper.js": [
- "40ab729b1f9555f751611e845fac6360d0cbdd30",
- []
- ],
"federatedcredential-get.html": [
"476f32688f91cede949edf2a1e650ef573525bd7",
[]
],
- "fencedframe-mark-signedin.html": [
- "681fcd6787525632b0e5f1f24b2ca308d7fe6b82",
- []
- ],
- "iframe-mark-signedin.html": [
- "4ca0125cde2e4ec7a404ba360becf22a66eb3e40",
- []
- ],
- "mark_signedin": [
- "d9adcaa762909e0ddef54daa989848169ede25dc",
- []
- ],
- "mark_signedin.sub.headers": [
- "d560fade5a0e4237f751b0c90a87fc25f92aa14a",
- []
- ],
- "mark_signedout": [
- "d9adcaa762909e0ddef54daa989848169ede25dc",
- []
- ],
- "mark_signedout.sub.headers": [
- "69157b3a371e369df585331a2479144fee444f5c",
- []
- ],
"otpcredential-helper.js": [
"e07e9f5be359e9173b6c916d91d8ce1f87260885",
[]
@@ -372361,14 +372839,6 @@
"passwordcredential-get.html": [
"0ec584d73d1f6d8626efe4c7190e5160fe527286",
[]
- ],
- "set_cookie": [
- "2c3196058a9e5213628821daf3a6c28ee18bd0d1",
- []
- ],
- "set_cookie.headers": [
- "df223115a7fa546e682275e8bb9d9af624ea333a",
- []
]
}
},
@@ -392738,6 +393208,10 @@
"3b9e23678d4eb08387423de7a0a0dfb5675445ef",
[]
],
+ "box-decoration-break-clone-009-ref.html": [
+ "23be78f87fcc373e74584c788b05b61a374803b3",
+ []
+ ],
"box-shadow-001-ref.html": [
"f87e16adedc779911ec081990acd76dd5f6ed5ad",
[]
@@ -393447,7 +393921,7 @@
[]
],
"color-mix-non-srgb-001-ref.html": [
- "15556737c7f8b10e80bcaec6bf550403050dd990",
+ "e143350866dc37125d840f1783780e324eaba152",
[]
],
"color-mix-percents-01-ref.html": [
@@ -394553,6 +395027,14 @@
"aad9b9b73efe854cb0324b55c461c51592cf7a00",
[]
],
+ "content-visibility-auto-print-ref.html": [
+ "6f500daa743380bf54f2aa3d5734322b370c5a43",
+ []
+ ],
+ "content-visibility-auto-svg-image-ref.html": [
+ "39939a6b33b7b83ef8da4c1dafe421380a2c57da",
+ []
+ ],
"content-visibility-canvas-ref.html": [
"dbedcf18ad7200f3595ba70607fb023a1373edb7",
[]
@@ -410340,6 +410822,10 @@
},
"clip-path": {
"animations": {
+ "clip-path-animation-cancel-ref.html": [
+ "661e7b3be8460934b425b1330b66f56f6c214096",
+ []
+ ],
"clip-path-animation-custom-timing-function-ref.html": [
"49732d361b3ff40f6232fa87423cf5a9e6530a55",
[]
@@ -412326,6 +412812,22 @@
"d794c76e3c9653dd94b2bad73cdf2a4574db5f50",
[]
],
+ "line-clamp-022-ref.html": [
+ "7df05662f07259544398fc1ac6673c2ee5165956",
+ []
+ ],
+ "line-clamp-023-ref.html": [
+ "55ca1ca92be48310cbdc4839573fa8f67ca84c2a",
+ []
+ ],
+ "line-clamp-026-ref.html": [
+ "b6e816f997c21186153ddd340916889b7ba7c53d",
+ []
+ ],
+ "line-clamp-027-ref.html": [
+ "01ed862e0a27147bf6be4b2c88e81f897b6ce34a",
+ []
+ ],
"line-clamp-auto-002-ref.html": [
"fe0a8dbd588a8a56c8ac0488713a061ef83474f9",
[]
@@ -412342,6 +412844,54 @@
"4a5f3536cc62c33e1a30ed9fce9a21cecda341bc",
[]
],
+ "line-clamp-auto-018-ref.html": [
+ "4d24d766956879e47ad7b36e4074993324204b8d",
+ []
+ ],
+ "line-clamp-auto-019-ref.html": [
+ "5dd739a1dabadff72e9ec19236f46a7850f2d83f",
+ []
+ ],
+ "line-clamp-auto-020-ref.html": [
+ "ef111f1e4a88a88c3b7f679d29f5bae58ea9624e",
+ []
+ ],
+ "line-clamp-auto-021-ref.html": [
+ "9ecad287453435b3b64e50366013caab453f0dfa",
+ []
+ ],
+ "line-clamp-auto-023-ref.html": [
+ "adb4b6dc6edf2a401cd4016f394c74b9bf9b89cb",
+ []
+ ],
+ "line-clamp-auto-025-ref.html": [
+ "a76468d6d546e1e58a15a604a2ae476cf8dfec27",
+ []
+ ],
+ "line-clamp-auto-026-ref.html": [
+ "44d503a05220fdf89caf4722961fc478ae943350",
+ []
+ ],
+ "line-clamp-auto-027-ref.html": [
+ "fa0ade7a61d0729dc216fe92fc9d1f49bcd31991",
+ []
+ ],
+ "line-clamp-auto-028-ref.html": [
+ "b70c6be4ffc13b1ebfa7c2dee33775053b599e53",
+ []
+ ],
+ "line-clamp-auto-029-ref.html": [
+ "76cd5fb7d22c8baf043dc379cc5486884d215df6",
+ []
+ ],
+ "line-clamp-auto-031-ref.html": [
+ "2d3c9d0261b7eb369d99dbd842309f697e80bad8",
+ []
+ ],
+ "line-clamp-auto-032-ref.html": [
+ "3b32f988cd91c6c3acf65a3368788a44d6850eea",
+ []
+ ],
"line-clamp-with-abspos-001-ref.html": [
"d756162dde0c54bd52646597b01bbff8a80f5fd8",
[]
@@ -413162,6 +413712,18 @@
"d8468e3b14dc0346f178727336572f5122b0e4bf",
[]
],
+ "page-background-001-print-ref.html": [
+ "8269582d04172ac593bea8026d74cc80128a91e1",
+ []
+ ],
+ "page-background-002-print-ref.html": [
+ "997a28c65acaf09babec8dd37ac9187af5d224e9",
+ []
+ ],
+ "page-background-003-print-ref.html": [
+ "93f3c7e95adc32a674f213cd273a887f76b57f20",
+ []
+ ],
"page-background-image-print-ref.html": [
"f38cc89838027f5f862cbd08459c1694f4a30e01",
[]
@@ -417742,6 +418304,10 @@
"9294283d5256978d94f1d4731d8d1ea39e6cb6b1",
[]
],
+ "collapsed-border-remove-row-group-ref.html": [
+ "060f0651b691a7db6c8cb2817c09201e0f8280b6",
+ []
+ ],
"floats": {
"floats-wrap-bfc-006b-ref.xht": [
"93cdd3e51cab23bd4f20fb1794ae7eeeb5ff21b1",
@@ -424150,6 +424716,10 @@
[]
]
},
+ "pseudo-element-transform-ref.html": [
+ "05ea20b69c4b45bb4c9eb49ffa10a342ab97b7ac",
+ []
+ ],
"reference": {
"transition-test-ref.html": [
"d9d91ec9da9e90feaf164632b22cb285f10a76ca",
@@ -435917,13 +436487,33 @@
}
},
"fedcm": {
+ "META.yml": [
+ "40b5b106f621aa5176d7a4292f0a26f85631aac8",
+ []
+ ],
+ "fedcm-cross-origin-policy.https.html.sub.headers": [
+ "32523a697886f7fb26ab4456e5b14cb19d8c1aae",
+ []
+ ],
+ "fedcm-csp.https.html.sub.headers": [
+ "c1e6fd6c4c281f22e7f92506ec72ca39b6293f23",
+ []
+ ],
+ "fedcm-opaque-rp-origin.https.html.headers": [
+ "9850d21f3c6fa83cd87c23f6b0f4150b64011918",
+ []
+ ],
"support": {
+ "README.md": [
+ "28a6d3453bda1d74b2c0f81f0a9ab23e71545503",
+ []
+ ],
"accounts.py": [
"c0117862816ff4668b6dd9416633bee6eb9af227",
[]
],
"accounts_check_same_site_strict.py": [
- "27a5d6a5b3e393bdf9b017a2adfd780c0c1d85bb",
+ "7bab26d3e9b8eafd0d9049bf7d6e952fb8249857",
[]
],
"accounts_no_approved_clients.py": [
@@ -435958,6 +436548,60 @@
"609d49c76bc269053d9827766483057bf66a7234",
[]
],
+ "fedcm": {
+ "disconnect-iframe.html": [
+ "f65763932b8c742debdbabbefc310613fe83f0e5",
+ []
+ ],
+ "intercept_service_worker.js": [
+ "b458c973be3083ef05c3dffda39944f6f4549d4b",
+ []
+ ],
+ "pending-userinfo-iframe.html": [
+ "da2cd26066abd87eaa04d5ade3845424d49de103",
+ []
+ ],
+ "simple.html": [
+ "d62419ce8a0ac12a85f5b8e595a874714b038b44",
+ []
+ ],
+ "userinfo-iframe.html": [
+ "64d5cb83a088db7dc8f3a9e7e2a65301eb0b64b1",
+ []
+ ]
+ },
+ "fedcm-helper.sub.js": [
+ "767c044818eab85a07c8240611730a885122a858",
+ []
+ ],
+ "fedcm-helper.sub.js.headers": [
+ "cb762eff806849df46dc758ef7b98b63f27f54c9",
+ []
+ ],
+ "fedcm-iframe-level2.html": [
+ "7622d988ff2c129003f0e3251bb7a0be698d17d6",
+ []
+ ],
+ "fedcm-iframe.html": [
+ "ba79c4cf9e955672d94450143dff091a6691a0e2",
+ []
+ ],
+ "fedcm-mock.js": [
+ "271dd9cd944a2770f27b119331dc77d919ba9cf5",
+ []
+ ],
+ "fedcm-mojojs-helper.js": [
+ "40ab729b1f9555f751611e845fac6360d0cbdd30",
+ []
+ ],
+ "fencedframe-mark-signedin.html": [
+ "681fcd6787525632b0e5f1f24b2ca308d7fe6b82",
+ []
+ ],
+ "iframe-mark-signedin.html": [
+ "4ca0125cde2e4ec7a404ba360becf22a66eb3e40",
+ []
+ ],
"keys.py": [
"6b7d67e21e7eea7927a40ab094847b7224d49985",
[]
@@ -436046,6 +436690,22 @@
"9e4af250045757a3ce7b58bc7d0347ecd2794a40",
[]
],
+ "mark_signedin": [
+ "d9adcaa762909e0ddef54daa989848169ede25dc",
+ []
+ ],
+ "mark_signedin.sub.headers": [
+ "d560fade5a0e4237f751b0c90a87fc25f92aa14a",
+ []
+ ],
+ "mark_signedout": [
+ "d9adcaa762909e0ddef54daa989848169ede25dc",
+ []
+ ],
+ "mark_signedout.sub.headers": [
+ "69157b3a371e369df585331a2479144fee444f5c",
+ []
+ ],
"no_accounts.py": [
"fad93088db5b9204c890fea871574c8b7d1ee4f8",
[]
@@ -436066,6 +436726,14 @@
"15adf11324ee6b7a0b03d402622e6dad82f677c5",
[]
],
+ "set_cookie": [
+ "2c3196058a9e5213628821daf3a6c28ee18bd0d1",
+ []
+ ],
+ "set_cookie.headers": [
+ "df223115a7fa546e682275e8bb9d9af624ea333a",
+ []
+ ],
"single_account.py": [
"000a23d925c8ce58e2b6d578badc3957cc8c9381",
[]
@@ -436083,7 +436751,7 @@
[]
],
"token_check_same_site_strict.py": [
- "f030b9b6fd5365baf5013931207345f13f835bde",
+ "20bdc5948776263a39c85dbe38fae2846d90222d",
[]
],
"token_with_account_id.py": [
@@ -437978,7 +438646,7 @@
[]
],
"post-to-owner.py": [
- "256dd6e49dcca90d503b9b7c2a4d1cf04cb330d0",
+ "2d4896867bb8f4708fa041f7677d85ad7c77de38",
[]
],
"record-header.py": [
@@ -442239,86 +442907,6 @@
]
}
},
- "post-message": {
- "resources": {
- "first-party-to-first-party-cross-partition-window.html": [
- "cdfa9d95caa64cd0bf7d5aec8da92630bef469f8",
- []
- ],
- "first-party-to-first-party-same-partition-window.html": [
- "4baf45d3c82a13a619c5fc0acfd4dc9a2a3834b0",
- []
- ],
- "first-party-to-third-party-cross-partition-cross-origin-iframe.https.html": [
- "eef594831aa3ae4e1c2afbaafdbe5b1054c3770a",
- []
- ],
- "first-party-to-third-party-cross-partition-cross-origin-window.sub.https.html": [
- "7edfbd93281ae014529a7f388ba88b58e04d5229",
- []
- ],
- "first-party-to-third-party-cross-partition-same-origin-iframe.html": [
- "eb17036c8cc347333e9397799153cf05f60362d5",
- []
- ],
- "first-party-to-third-party-cross-partition-same-origin-window.sub.html": [
- "f99b96c4667824e61c8f704783f819c4ea95b107",
- []
- ],
- "third-party-to-first-party-cross-partition-cross-origin-iframe.sub.html": [
- "348efb3f9c4acbe8d92fba3c313dbcc0670f9b2c",
- []
- ],
- "third-party-to-first-party-cross-partition-cross-origin-window.https.html": [
- "00459094941ea6b9ec28d81324ec566f80f1fff6",
- []
- ],
- "third-party-to-first-party-cross-partition-same-origin-iframe.sub.html": [
- "405b1053d33da9e4f21504308b51fc5c69ee34a5",
- []
- ],
- "third-party-to-first-party-cross-partition-same-origin-window.html": [
- "0ae51e3fc173ac7c7bbea2deea53eaf5a2adec7b",
- []
- ],
- "third-party-to-third-party-cross-partition-cross-origin-iframe-a.sub.html": [
- "a11f3640e83290f1cf1987895916e4c25803693f",
- []
- ],
- "third-party-to-third-party-cross-partition-cross-origin-iframe-b.https.html": [
- "f56e2b35d8b72ae9a72eb2bbc8b81ed5881eb6e8",
- []
- ],
- "third-party-to-third-party-cross-partition-cross-origin-window.sub.https.html": [
- "13072875879de7f6fa4bbd6331ffbfd3f478fe77",
- []
- ],
- "third-party-to-third-party-cross-partition-same-origin-iframe-a.sub.https.html": [
- "ec551bfdec9384e0316c5d6177140cfa29233e00",
- []
- ],
- "third-party-to-third-party-cross-partition-same-origin-iframe-b.https.html": [
- "fc15b1f12523311abdd6d33092609110388b1db6",
- []
- ],
- "third-party-to-third-party-cross-partition-same-origin-window.sub.https.html": [
- "f26f709db34e83796412c6e9128f7f6c4b10d894",
- []
- ],
- "third-party-to-third-party-same-partition-iframe-a.sub.html": [
- "339dc9f06eb791ca96abcf15d48ace15a137a01f",
- []
- ],
- "third-party-to-third-party-same-partition-iframe-b.html": [
- "daaf236bfca6a33363f5a50ca6cd038b1c15aefc",
- []
- ],
- "third-party-to-third-party-same-partition-window.sub.html": [
- "7cea58f235cf66dfa6c8d98b781593314ed06e2a",
- []
- ]
- }
- },
"resources": {
"browsing-context-window.html": [
"c1594f637ec50699ea4b341d35ea2a43e9d0f549",
@@ -444801,7 +445389,7 @@
[]
],
"text.yaml": [
- "49edd754c85b92229a45fd3b5b610190cd287804",
+ "240a04ce8dc47b2d052809c1c72284ee169895e2",
[]
],
"the-canvas-state.yaml": [
@@ -445425,7 +446013,7 @@
},
"resources": {
"reporting-common.js": [
- "70bb4897f5082d84bd26f618dd8212d8f17e0862",
+ "accb6faaec5bec26e4a3f648e1d3dba66d12ef27",
[]
],
"test-access-property.js": [
@@ -445467,6 +446055,10 @@
"f13e76869023ef5604ab7be083a37b6ff05b1b85",
[]
],
+ "noopener-helper.js": [
+ "1f9b07750715354fc91d5091970c5a00b1362146",
+ []
+ ],
"popup-test.js": [
"c2717bb13546b168374cb37ac5be12a0328dd51c",
[]
@@ -445662,6 +446254,10 @@
"94e9a4f1908e4f4eb3216981a91c3c8174902fcb",
[]
],
+ "cdata-iframe.xhtml": [
+ "0c48cbef4fc323dd35ceae449f5494325fe4e1d3",
+ []
+ ],
"dir-auto-dynamic-simple-ref.html": [
"5a9a2f2484cef7a53fee96058ba7c61926b8a3c6",
[]
@@ -445835,7 +446431,7 @@
[]
],
"dir-shadow-utils.js": [
- "c7d89cf908286bf2ebad72abc59abe26949a209a",
+ "26fa30efe8f830f049d2c7c44a79b1dbcac35eaf",
[]
],
"dir_auto-EN-L-ref.html": [
@@ -452849,7 +453445,7 @@
[]
],
"grouping-li-reftest-list-owner-menu-ref.html": [
- "2b1ea76656fdcacad2505c48d69f377a1a0e47d6",
+ "fce5538f5e01a2053edcde67fe48f24f48e1c7e3",
[]
],
"grouping-li-reftest-list-owner-mixed-ref.html": [
@@ -458732,7 +459328,7 @@
[]
],
"invokers.tentative.idl": [
- "eb1b8247f06e9681cef5c0a2eb8cf71530a5dbd9",
+ "4724d7deb08d0110df3a47b8d6542b719ceae9dd",
[]
],
"is-input-pending.idl": [
@@ -459757,7 +460353,7 @@
]
},
"lint.ignore": [
- "170f4136d4b4f60827f37965152be573671d28fc",
+ "ca22175f33cf197d13a9fe0ef7d67b474579e30f",
[]
],
"loading": {
@@ -460027,6 +460623,10 @@
"c73b604f6dcabb4b0c6247f53a524c05c6344194",
[]
],
+ "long-pointerdown.html": [
+ "34e8115086c1f65822b0d0795ace3e6d488b7fac",
+ []
+ ],
"promise-generates-loaf.js": [
"53369f722606e3fbc62b6c1f2568b90b67655e97",
[]
@@ -460040,7 +460640,7 @@
[]
],
"utils.js": [
- "aa537d39a780d9d402aebc49c43c2ed733ef343b",
+ "b51ab891dcdb820dcb554236d36ac1c3476e556b",
[]
]
}
@@ -460979,6 +461579,10 @@
"padding-border-margin-003-ref.html": [
"275494ddd8c5cf0ae39b7402242aedc9b000a6b8",
[]
+ ],
+ "padding-border-margin-004-ref.html": [
+ "4da9af7ffc34487ee43971c1e592cfc7ef72531f",
+ []
]
},
"presentational-hints-001-ref.html": [
@@ -466641,7 +467245,7 @@
[]
],
"web-bluetooth-test.js": [
- "ecea5e760c6b4d4f867adf5741a929cccf5447b9",
+ "67c3c4ee1b45330b381cbd16bce025399c99b93e",
[]
],
"web-bluetooth-test.js.headers": [
@@ -467630,6 +468234,10 @@
[]
],
"caret": {
+ "after-designMode-off-ref.html": [
+ "fbb3c2467e8cfe4e37083609048f89f46029a66a",
+ []
+ ],
"collapse-pre-linestart-ref.html": [
"2b25941ded7e51d4a62cfc536f42de0aea58a50c",
[]
@@ -471953,11 +472561,11 @@
[]
],
"storage-access-beyond-cookies-iframe-iframe.html": [
- "cfc7d599a1b5c7394233a6527c65b8ea9879a986",
+ "44ac9cd8986067b611196836210511b07bbb2a61",
[]
],
"storage-access-beyond-cookies-iframe.sub.html": [
- "3d9f5bb1ef2499e8fbf84a83037f10335973930e",
+ "7d43a06bc7eeb942c498f52856eb7e50b37b26b0",
[]
]
}
@@ -473507,7 +474115,7 @@
[]
],
"requirements_tc.txt": [
- "a9128c02eb93c807bcb331e4ee09df9cbc4fd278",
+ "9bc3c840ab4a035ab39033fc241effb8f98fe79b",
[]
],
"run_tc.py": [
@@ -473549,7 +474157,7 @@
],
"tasks": {
"test.yml": [
- "a9ca07c6cebb887e44f7f1effbbd690e8bc98e82",
+ "fffb146dacc792d9ac9da2caf557cb8ec3ebdec4",
[]
]
},
@@ -473623,7 +474231,7 @@
[]
],
"README.md": [
- "bc98d198612c9afdcbba991b120ba9dc473d29c7",
+ "9ec7a99cfff1620ac65c62f5c84cefcffe098b5a",
[]
],
"__init__.py": [
@@ -473635,7 +474243,7 @@
[]
],
"frontend.py": [
- "f91e79fa78e493edb2dd87bdbaf1bc490d225836",
+ "05f4e0fc8db710bc00c242c32ee4db6fe4b43659",
[]
],
"requirements.txt": [
@@ -474110,15 +474718,15 @@
[]
],
"requirements_mypy.txt": [
- "2addbba3b226b77cfdde985bf484582cc87000d2",
+ "66ae1420387a6af8df07a4f53d2c7b837d47d551",
[]
],
"requirements_pytest.txt": [
- "75d70d49bd8006c97855a3528418c033c965c626",
+ "14cc97014aa256f67c0c95369306fe9bf42f72b9",
[]
],
"requirements_tests.txt": [
- "74792e6131456c571fadd096ae0a53e0edfb0d93",
+ "c43af1ba8baa21070ee27e4f88858d772c2f9bfe",
[]
],
"runner": {
@@ -484409,7 +485017,7 @@
[]
],
"client.py": [
- "ee3ef1ea95d5aeff7cdc14f378b0a975b8d94b4d",
+ "d4cc78588e9fd819e96e8f06a44f57775ccb3d6b",
[]
],
"error.py": [
@@ -484438,7 +485046,7 @@
[]
],
"network.py": [
- "46c5885271afd28cb3297209d1b281b0b34e4e00",
+ "dc895d9834c4167e1f42c81b85fecdab2ef78b50",
[]
],
"permissions.py": [
@@ -484480,7 +485088,7 @@
[]
],
"transport.py": [
- "ca1ff74ef96767afa76c200b159671d99762cb84",
+ "7e898b2869f2a87add6a79312d778b9a3774b7d1",
[]
]
}
@@ -485047,10 +485655,6 @@
"1cc80a270e2c1e8f0bcb8dc4878a75d9ce01717a",
[]
],
- "testharnessreport-content-shell.js": [
- "1e24fdbcfbf05fee3d56d16dffef7bf35cb9874f",
- []
- ],
"testharnessreport-servo.js": [
"d6616739e6ed63a79e8b2f5d8aee1d5b2ced7f49",
[]
@@ -488676,13 +489280,13 @@
[]
]
},
- "set_cache_bypass": {
+ "set_cache_behavior": {
"__init__.py": [
"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
[]
],
"conftest.py": [
- "969641b8fd202df97753e0c6221d0db20af42827",
+ "1f12c9fcb8435fd41d2c2e31678464220c26d94a",
[]
]
},
@@ -489726,7 +490330,7 @@
[]
],
"conv_transpose2d.json": [
- "d15c4a8461565b530050b4168d95bf1250da142e",
+ "ba25e04094cbe9b19e7c41f29681c970985a7d36",
[]
],
"cos.json": [
@@ -489979,7 +490583,7 @@
]
},
"utils.js": [
- "b8a017fb44bfe4748f7ab7c78aff3333373baf1d",
+ "5008c008e6802ade60ac436fed44c7c6b425f964",
[]
],
"utils_validation.js": [
@@ -509347,7 +509951,7 @@
]
],
"comp_tooltip.html": [
- "deaabefd93accf622f98f3ec3f5def5cb9b3e0d6",
+ "c12f9c79fa0fef62d2f82a8a2053c828524a75e4",
[
null,
{
@@ -509698,7 +510302,7 @@
]
],
"simple-trigger-aggregatable-debug-report.sub.https.html": [
- "4dbf7b39a7b5979468ea83d3293b6fb73c4581f5",
+ "68eae824a295020f45a1d2e37a8ab0a54e9b473d",
[
null,
{
@@ -530603,495 +531207,6 @@
{}
]
],
- "fedcm-abort.https.html": [
- "0f03bff832f800c9ce69de69003c88bbbd471fca",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-after-abort.https.html": [
- "3c2f981e82fab32d20303367289180c20e8eb71f",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-authz": {
- "fedcm-continue-on-disallowed.https.html": [
- "fcda3a3dd591c694d341f5a04fa42ce667ea1ad6",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-continue-on-with-account.https.html": [
- "5bd8ef34fe83e53c03894fcc48134f5aea01a286",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-continue-on.https.html": [
- "c7da5384af43effd31d67b4e6f2ee3643a3f9bbe",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-disclosure-text-shown.https.html": [
- "513ef258e18a685e33c7f976c8a16cd2cd594b74",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-userinfo-after-resolve.https.html": [
- "0521f4a2ab5d82aeaca1f603b297e1199b886af8",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "resolve-after-preventsilentaccess.https.html": [
- "7223a722fe2fb4e533acb3e4777a53669fdb1d5c",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ]
- },
- "fedcm-auto-reauthn-without-approved-clients.https.html": [
- "fb93cb632db09dfd3332ac6008f1f40253361ea6",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-auto-selected-flag.https.html": [
- "d06aba73bc1de9a9b6e238695587c1ff47480de3",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-basic.https.html": [
- "3d20f4cfb74d5a356b920d224415f622a67d446f",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-button-and-other-account": {
- "fedcm-button-mode-basics.tentative.https.html": [
- "a71e26213524928d01f552f6dcf522247636619d",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-button-mode-priority.tentative.https.html": [
- "b71e84db47e458f42931cfcbcc6e0100cd1d40d3",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-use-other-account-button-flow.tentative.https.html": [
- "7a3f266b24b937fae03c9886184ada6e8b27c960",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-use-other-account.tentative.https.html": [
- "66311740124a2dd6aff0c8f90aff68f289724cd6",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ]
- },
- "fedcm-client-metadata-not-cached.https.html": [
- "79171bf6343287fa42c0aaa1c53a2d4e69899425",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-context.https.html": [
- "f235437b789aee4dff2ecb639cd85f9c16f663a9",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-cross-origin-policy.https.html": [
- "1e3b4c71a839b5f02f19fe68639cd95c0f32c8fc",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-csp.https.html": [
- "c9a2456e4d082c6c77942d76dc0fee039015161b",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-disconnect-errors.https.html": [
- "4d5fb0a457c7acd0d5c23818f4623826324f7336",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-disconnect-iframe.sub.https.html": [
- "3d31be60b18b095889354ef4c916221633ad7ad3",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-disconnect.sub.https.html": [
- "2ea2d4a2599751cad941552964c700ced2f1b7cc",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-domainhint.https.html": [
- "20b4569a05e67d47a2aba9913bd330ceef98aef4",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-endpoint-redirects.https.html": [
- "71dbce03267e86e00fd90e1b69abf186b0fdd3a3",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-error-basic.https.html": [
- "8a2d39cabaaa37a95f050cc84af2fe2c786ec825",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-identity-assertion-nocors.https.html": [
- "612387b4a0dc69a428f85333868b7f9877c76a81",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-iframe.https.html": [
- "6a9bec677cc67e07fd25de0d3f3c9d7ea9d7580f",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-login-status": {
- "confirm-idp-login.https.html": [
- "0f8df72b615b5fadb9e17decd69c670652d06d21",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "cross-origin-status.https.html": [
- "f32e18d40eda749d24d1a48e7f16d2ab33c262ce",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "logged-out.https.html": [
- "09750ff0968a76b47815a0c299d1bb3bc535733f",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ]
- },
- "fedcm-login-status-unknown.https.html": [
- "d542524c884790fe6a957ffa789d8336284df252",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-loginhint.https.html": [
- "fe35007a87dc06a9062e6a81fcd6558f102782b7",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-manifest-not-in-list.https.html": [
- "087af384b153afb5204cb0f3dc575bed57055335",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-multi-idp": {
- "fedcm-multi-idp-abort.https.html": [
- "712a7b6a3494cbff372410f2517d8d9b14c5d49e",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-multi-idp-basic.https.html": [
- "d855e0ad8dcf4eb86ce4bf3016337540bfafd45c",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-multi-idp-context.https.html": [
- "1bc3eb1f56295fef925c0d4bf0ea2aab46ed53ab",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-multi-idp-mediation-optional.https.html": [
- "1a819efb314fa12eb0087a85682953f6a1705b29",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-multi-idp-mediation-silent.https.html": [
- "d47d4898c7d72b1acde2e5486902ce5bdf966c93",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ]
- },
- "fedcm-no-login-url.https.html": [
- "94592d2dbfbfb9f308f4aa97a198816119a36502",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-nonce-is-optional.https.html": [
- "dafd6c9e983053b50c12b56ce2ef483c61006c77",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-not-observed-by-service-worker.https.html": [
- "072d66966566c939bfa5ba64db1d62e7247dab81",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-opaque-rp-origin.https.html": [
- "228646e0fee4387261ff8e30db38cdf3cbefcaa9",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-pending-call-rejected.https.html": [
- "bb9f885a8a1b9b4ad231774f6936fc99c701b255",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-pending-disconnect.https.html": [
- "1b60acc10880ddf359aeeb5974f2f93e82df641c",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-pending-userinfo.https.html": [
- "0ecae3e80a4bb9f3fbe27ff32284a5f503a099b1",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-register": {
- "fedcm-no-registered-idps.https.html": [
- "7be2d397e6871961b347e89ca5aebc050a60c7ae",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ]
- },
- "fedcm-reject-invalid-responses.https.html": [
- "f450d568249ec7d7f2ef6f1229d5781344cfb5c5",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-returning-account-auto-reauthn.https.html": [
- "c9fe10a485fbe2dee7508f6d69801f67ca2c6208",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-same-site-none": {
- "fedcm-same-site-none.https.html": [
- "d3d20ea9df270cc23c48f7f709c9cc21bab7990e",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ]
- },
- "fedcm-store.https.html": [
- "d1e6ef464c4f3eb8e2d4118d1a5e2dc498410e05",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-token-returned-with-http-error.https.html": [
- "7c7687f00f344dd42639a8be3590545a93adf882",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-too-many-disconnect-calls.https.html": [
- "eb87c2377a916b68e1ebf189a0de7d8af1c191df",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
- "fedcm-userinfo.https.html": [
- "d460d82845282f00e0164590c552bcd73ca15255",
- [
- null,
- {
- "testdriver": true
- }
- ]
- ],
"federatedcredential-framed-get.sub.https.html": [
"7883c8ebf23ea3c4214db585aba32d391d824a52",
[
@@ -534639,6 +534754,13 @@
{}
]
],
+ "display-none-prevents-starting-in-subtree.html": [
+ "d5d48b2a1849c3173d35db7b62ea36a207972308",
+ [
+ null,
+ {}
+ ]
+ ],
"empty-pseudo-class-with-animation.html": [
"d8dba1ab5bdae3b752004ae7112d680582cd010f",
[
@@ -561394,6 +561516,13 @@
{}
]
],
+ "transitions-retarget.html": [
+ "ee4c54a0b19f39a888b1e157ec2127084cf35500",
+ [
+ null,
+ {}
+ ]
+ ],
"zero-duration-multiple-transition.html": [
"7b85702092a988aaced7070198252dda1891e40d",
[
@@ -564772,6 +564901,13 @@
"timeout": "long"
}
]
+ ],
+ "interpolate-size-interpolation.html": [
+ "ef2034ba4802b886dbac278ea0b6b5a616a68404",
+ [
+ null,
+ {}
+ ]
]
},
"calc-size-height.tentative.html": [
@@ -564801,6 +564937,20 @@
null,
{}
]
+ ],
+ "interpolate-size-computed.html": [
+ "ca7fd706cb96e3f79d967d07516cbc1230a5007f",
+ [
+ null,
+ {}
+ ]
+ ],
+ "interpolate-size-parsing.html": [
+ "04c8cff8f360983e7897698556cbd08916fe1ae4",
+ [
+ null,
+ {}
+ ]
]
},
"calc-time-values.html": [
@@ -572397,7 +572547,7 @@
]
],
"label-delegatesFocus.html": [
- "74d31363c98b42333d5841a4dc6e5d14aa58c6b9",
+ "6f854faeb66b274e08986823480cc5d5ab1569d7",
[
null,
{
@@ -577291,7 +577441,7 @@
]
],
"observable-constructor.any.js": [
- "2cd2ee2b6641a71bfe5de2309b73ea9918a6321a",
+ "109ed284db0445163bd4979172f4cb523855eb66",
[
"dom/observable/tentative/observable-constructor.any.html",
{}
@@ -577349,7 +577499,7 @@
]
],
"observable-filter.any.js": [
- "3c1a7d782488c8d7d2cfbfb0da0b26fc41bb70b0",
+ "419d59ed8a8c49486e9ef90fbad3b087b126390d",
[
"dom/observable/tentative/observable-filter.any.html",
{}
@@ -577371,7 +577521,7 @@
]
],
"observable-first.any.js": [
- "7c99066dc22a82a9043349934b28f939187b3d39",
+ "d4738d7478b1eac573dd8a604bd5c033e0193030",
[
"dom/observable/tentative/observable-first.any.html",
{}
@@ -577433,7 +577583,7 @@
]
],
"observable-last.any.js": [
- "cd39a3700a211aad77e673a2e096ecdb2bb8c8e9",
+ "064a781cada985de1e2f5eeae41898cbff1aad5b",
[
"dom/observable/tentative/observable-last.any.html",
{}
@@ -577444,7 +577594,7 @@
]
],
"observable-map.any.js": [
- "275505fb5d02fd4de353fa44311fdc696e3772be",
+ "a61c818bc16539f57e246126daaed64e0537638b",
[
"dom/observable/tentative/observable-map.any.html",
{}
@@ -577473,7 +577623,7 @@
]
],
"observable-switchMap.any.js": [
- "836a39a68e00f15c7e42a80354feb9ad7842c22d",
+ "577ce2b748cd0c668a1d305d92462f47c8a894e7",
[
"dom/observable/tentative/observable-switchMap.any.html",
{}
@@ -577495,7 +577645,7 @@
]
],
"observable-takeUntil.any.js": [
- "2895dd31e3f2aac80afc4621b6cee444b32a3eec",
+ "f2e99b8cbec88eeda799e46d695e888143f9cbf6",
[
"dom/observable/tentative/observable-takeUntil.any.html",
{}
@@ -577513,7 +577663,7 @@
]
],
"observable-toArray.any.js": [
- "9e6e3abee561de07d814925a57873353ab0b32ae",
+ "582bc67453ecbe03d39759f17d661485f5b688d3",
[
"dom/observable/tentative/observable-toArray.any.html",
{}
@@ -597183,6 +597333,497 @@
]
}
},
+ "fedcm": {
+ "fedcm-abort.https.html": [
+ "0f03bff832f800c9ce69de69003c88bbbd471fca",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-after-abort.https.html": [
+ "3c2f981e82fab32d20303367289180c20e8eb71f",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-authz": {
+ "fedcm-continue-on-disallowed.https.html": [
+ "fcda3a3dd591c694d341f5a04fa42ce667ea1ad6",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-continue-on-with-account.https.html": [
+ "5bd8ef34fe83e53c03894fcc48134f5aea01a286",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-continue-on.https.html": [
+ "c7da5384af43effd31d67b4e6f2ee3643a3f9bbe",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-disclosure-text-shown.https.html": [
+ "513ef258e18a685e33c7f976c8a16cd2cd594b74",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-userinfo-after-resolve.https.html": [
+ "e92f48988637e55f4b3f12d826c2aebb81567bc2",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "resolve-after-preventsilentaccess.https.html": [
+ "7223a722fe2fb4e533acb3e4777a53669fdb1d5c",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ]
+ },
+ "fedcm-auto-reauthn-without-approved-clients.https.html": [
+ "fb93cb632db09dfd3332ac6008f1f40253361ea6",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-auto-selected-flag.https.html": [
+ "d06aba73bc1de9a9b6e238695587c1ff47480de3",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-basic.https.html": [
+ "3d20f4cfb74d5a356b920d224415f622a67d446f",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-button-and-other-account": {
+ "fedcm-button-mode-basics.tentative.https.html": [
+ "a71e26213524928d01f552f6dcf522247636619d",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-button-mode-priority.tentative.https.html": [
+ "b71e84db47e458f42931cfcbcc6e0100cd1d40d3",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-use-other-account-button-flow.tentative.https.html": [
+ "7a3f266b24b937fae03c9886184ada6e8b27c960",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-use-other-account.tentative.https.html": [
+ "66311740124a2dd6aff0c8f90aff68f289724cd6",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ]
+ },
+ "fedcm-client-metadata-not-cached.https.html": [
+ "79171bf6343287fa42c0aaa1c53a2d4e69899425",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-context.https.html": [
+ "f235437b789aee4dff2ecb639cd85f9c16f663a9",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-cross-origin-policy.https.html": [
+ "1e3b4c71a839b5f02f19fe68639cd95c0f32c8fc",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-csp.https.html": [
+ "c9a2456e4d082c6c77942d76dc0fee039015161b",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-disconnect-errors.https.html": [
+ "4d5fb0a457c7acd0d5c23818f4623826324f7336",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-disconnect-iframe.sub.https.html": [
+ "275a4a55c5efe5c17bc1dfe417c098a9ac19b166",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-disconnect.sub.https.html": [
+ "2ea2d4a2599751cad941552964c700ced2f1b7cc",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-domainhint.https.html": [
+ "20b4569a05e67d47a2aba9913bd330ceef98aef4",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-endpoint-redirects.https.html": [
+ "71dbce03267e86e00fd90e1b69abf186b0fdd3a3",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-error-basic.https.html": [
+ "8a2d39cabaaa37a95f050cc84af2fe2c786ec825",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-identity-assertion-nocors.https.html": [
+ "612387b4a0dc69a428f85333868b7f9877c76a81",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-iframe.https.html": [
+ "6a9bec677cc67e07fd25de0d3f3c9d7ea9d7580f",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-login-status": {
+ "confirm-idp-login.https.html": [
+ "0f8df72b615b5fadb9e17decd69c670652d06d21",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "cross-origin-status.https.html": [
+ "dc0ff948904d7319ed7aef67734055054139630a",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "logged-out.https.html": [
+ "4d11e64ff37cd8b18665167914adb12167e260f6",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ]
+ },
+ "fedcm-login-status-unknown.https.html": [
+ "d542524c884790fe6a957ffa789d8336284df252",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-loginhint.https.html": [
+ "fe35007a87dc06a9062e6a81fcd6558f102782b7",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-manifest-not-in-list.https.html": [
+ "087af384b153afb5204cb0f3dc575bed57055335",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-multi-idp": {
+ "fedcm-multi-idp-abort.https.html": [
+ "712a7b6a3494cbff372410f2517d8d9b14c5d49e",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-multi-idp-basic.https.html": [
+ "d855e0ad8dcf4eb86ce4bf3016337540bfafd45c",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-multi-idp-context.https.html": [
+ "1bc3eb1f56295fef925c0d4bf0ea2aab46ed53ab",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-multi-idp-mediation-optional.https.html": [
+ "1a819efb314fa12eb0087a85682953f6a1705b29",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-multi-idp-mediation-silent.https.html": [
+ "d47d4898c7d72b1acde2e5486902ce5bdf966c93",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ]
+ },
+ "fedcm-no-login-url.https.html": [
+ "94592d2dbfbfb9f308f4aa97a198816119a36502",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-nonce-is-optional.https.html": [
+ "dafd6c9e983053b50c12b56ce2ef483c61006c77",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-not-observed-by-service-worker.https.html": [
+ "a6537add10612c26ccb36e6b118a98c2b04d1b6b",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-opaque-rp-origin.https.html": [
+ "228646e0fee4387261ff8e30db38cdf3cbefcaa9",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-pending-call-rejected.https.html": [
+ "bb9f885a8a1b9b4ad231774f6936fc99c701b255",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-pending-disconnect.https.html": [
+ "1b60acc10880ddf359aeeb5974f2f93e82df641c",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-pending-userinfo.https.html": [
+ "1c85d4dc57de343b4ac09763de4b84a280f39ef4",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-register": {
+ "fedcm-no-registered-idps.https.html": [
+ "7be2d397e6871961b347e89ca5aebc050a60c7ae",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ]
+ },
+ "fedcm-reject-invalid-responses.https.html": [
+ "f450d568249ec7d7f2ef6f1229d5781344cfb5c5",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-returning-account-auto-reauthn.https.html": [
+ "c9fe10a485fbe2dee7508f6d69801f67ca2c6208",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-same-site-none": {
+ "fedcm-same-site-none.https.html": [
+ "d3d20ea9df270cc23c48f7f709c9cc21bab7990e",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ]
+ },
+ "fedcm-store.https.html": [
+ "d1e6ef464c4f3eb8e2d4118d1a5e2dc498410e05",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-token-returned-with-http-error.https.html": [
+ "7c7687f00f344dd42639a8be3590545a93adf882",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-too-many-disconnect-calls.https.html": [
+ "eb87c2377a916b68e1ebf189a0de7d8af1c191df",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ],
+ "fedcm-userinfo.https.html": [
+ "9f7be9fbbf33fffea1be9f62c49ddba05a6fd051",
+ [
+ null,
+ {
+ "testdriver": true
+ }
+ ]
+ ]
+ },
"fenced-frame": {
"add-fencedframe-to-detached-iframe.https.html": [
"37c0cd6cba832d73183b7c898d7c4319b47e111d",
@@ -626261,7 +626902,7 @@
},
"geolocation": {
"PositionOptions.https.html": [
- "e54e8679aa9d16cca2c83ac4aba50c286355010e",
+ "73d84738bd91ef4aee541df0f0f5da0b63a15482",
[
null,
{
@@ -626392,7 +627033,7 @@
]
],
"tojson.https.window.js": [
- "f8cef07a7f0d4325914fbf07bc34c681804b9d51",
+ "b3f8500b1a81beeda1f2b7407fcccc21666fd9fb",
[
"geolocation/tojson.https.window.html",
{
@@ -626791,7 +627432,7 @@
]
],
"raf-coarsened-time.https.html": [
- "e649d91c16311e289f179c5adf2970c4ac8bc22f",
+ "182c707dc3b783c755bc12ec53bc54a1fd2cead8",
[
null,
{}
@@ -635547,71 +636188,6 @@
{}
]
],
- "post-message": {
- "first-party-to-first-party-cross-partition.sub.html": [
- "f91d9403ea0ce424f92960ded05254c32e995430",
- [
- null,
- {}
- ]
- ],
- "first-party-to-first-party-same-partition.html": [
- "6fc915298bba3ef28ce991bf7dab69d74adc9199",
- [
- null,
- {}
- ]
- ],
- "first-party-to-third-party-cross-partition-cross-origin.sub.html": [
- "de776f83818be66d195e026a8d12986135a9c9ff",
- [
- null,
- {}
- ]
- ],
- "first-party-to-third-party-cross-partition-same-origin.sub.html": [
- "490b647fa4ac7e4e7b409336aa2f25ef0fec6c1a",
- [
- null,
- {}
- ]
- ],
- "third-party-to-first-party-cross-partition-cross-origin.sub.html": [
- "2618e966de6f403f7e0fdb156304695a63bf9748",
- [
- null,
- {}
- ]
- ],
- "third-party-to-first-party-cross-partition-same-origin.sub.html": [
- "735b4588411fe6bf5c670f9a0273388580104300",
- [
- null,
- {}
- ]
- ],
- "third-party-to-third-party-cross-partition-cross-origin.sub.html": [
- "1083071f7d2a21cca5dcb9b31621370a3c830bf4",
- [
- null,
- {}
- ]
- ],
- "third-party-to-third-party-cross-partition-same-origin.sub.html": [
- "9caf6c11e48f5dd8faef5f840f13cff0bf14659f",
- [
- null,
- {}
- ]
- ],
- "third-party-to-third-party-same-partition.sub.html": [
- "c90a05526846505b7bc73be1862f6652b7a8e02d",
- [
- null,
- {}
- ]
- ]
- },
"targeting-cross-origin-nested-browsing-contexts.html": [
"44d4fbad6b79a0dcbb7080c0b8caa529bc53426d",
[
@@ -642894,6 +643470,27 @@
{}
]
],
+ "2d.text.measure.caret-position-edge-cases.tentative.html": [
+ "b1d4b87397388cf298d75ae1634e5e2838c829af",
+ [
+ null,
+ {}
+ ]
+ ],
+ "2d.text.measure.caret-position-edges.tentative.html": [
+ "3a772bfd6cd5e15243ae45d39e82024ecf83f967",
+ [
+ null,
+ {}
+ ]
+ ],
+ "2d.text.measure.caret-position.tentative.html": [
+ "53c91264d7c678e63e8f24fa833c57610ee222a5",
+ [
+ null,
+ {}
+ ]
+ ],
"2d.text.measure.emHeights-low-ascent.html": [
"7b6874d10b5088f75fdfe44d74a43e197637b5ed",
[
@@ -642943,22 +643540,43 @@
{}
]
],
+ "2d.text.measure.getActualBoundingBox-exceptions.tentative.html": [
+ "8ce7ed79482813b9a148d2ddbb704f88a72090e3",
+ [
+ null,
+ {}
+ ]
+ ],
+ "2d.text.measure.getActualBoundingBox-full-text.tentative.html": [
+ "510bce84f2861f281e931881a7da87cd70473740",
+ [
+ null,
+ {}
+ ]
+ ],
+ "2d.text.measure.getActualBoundingBox.tentative.html": [
+ "6f3b66a355beb8676b680cd4a06f349d0e29f3d1",
+ [
+ null,
+ {}
+ ]
+ ],
"2d.text.measure.selection-rects-baselines.tentative.html": [
- "6c634b93190d4df1f0825d933ebe52633f6cbfdc",
+ "4c6f82889835d89f73e4c65f2e7f82c1b5240994",
[
null,
{}
]
],
"2d.text.measure.selection-rects-exceptions.tentative.html": [
- "dd213d3e83bff6766c9affd2a42e1e9e2166fb5a",
+ "565834280e5673121a9e3ec8566fd1e8542d9e9c",
[
null,
{}
]
],
"2d.text.measure.selection-rects.tentative.html": [
- "e724586f083af221a6ad454722a7af9c5644f5ed",
+ "9de40eb61f2ac54eb4833d886baa8f236327d104",
[
null,
{}
@@ -656359,6 +656977,41 @@
{}
]
],
+ "2d.text.measure.caret-position-edge-cases.tentative.html": [
+ "21139126fa9f7f22e7494c84ec3c95e28bc0e50c",
+ [
+ null,
+ {}
+ ]
+ ],
+ "2d.text.measure.caret-position-edge-cases.tentative.worker.js": [
+ "835392228363b63c175f6d9e17bd247bbe67dffe",
+ [
+ "html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.worker.html",
+ {}
+ ]
+ ],
+ "2d.text.measure.caret-position-edges.tentative.html": [
+ "29f47bf50908584c8dc1c3a5fe9334ec698fb354",
+ [
+ null,
+ {}
+ ]
+ ],
+ "2d.text.measure.caret-position-edges.tentative.worker.js": [
+ "2cef8432d2a2eb6fa9057d6a593cc7e2f0a2ae3f",
+ [
+ "html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.worker.html",
+ {}
+ ]
+ ],
+ "2d.text.measure.caret-position.tentative.html": [
+ "53faccd652aace13ab028ba371e7b6bf2769a88f",
+ [
+ null,
+ {}
+ ]
+ ],
"2d.text.measure.emHeights-low-ascent.html": [
"2e2e5dde6723d43ec9ff96d75bbe13194ea5a32e",
[
@@ -656457,36 +657110,78 @@
{}
]
],
+ "2d.text.measure.getActualBoundingBox-exceptions.tentative.html": [
+ "8b41d0c830ebcaaa493756f704ab1cc730964b82",
+ [
+ null,
+ {}
+ ]
+ ],
+ "2d.text.measure.getActualBoundingBox-exceptions.tentative.worker.js": [
+ "84bc720399c9d701157aa3eacea1190e7443493e",
+ [
+ "html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.worker.html",
+ {}
+ ]
+ ],
+ "2d.text.measure.getActualBoundingBox-full-text.tentative.html": [
+ "44b107e086d1798fa87ff033647603149455dffe",
+ [
+ null,
+ {}
+ ]
+ ],
+ "2d.text.measure.getActualBoundingBox-full-text.tentative.worker.js": [
+ "54a4cb41154e36f81e4c2f77662b265e0a4fd7cf",
+ [
+ "html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.worker.html",
+ {}
+ ]
+ ],
+ "2d.text.measure.getActualBoundingBox.tentative.html": [
+ "e6a2b0702055e82bc4c8226279d8af89e60e6bc4",
+ [
+ null,
+ {}
+ ]
+ ],
+ "2d.text.measure.getActualBoundingBox.tentative.worker.js": [
+ "9530d1776588e726f9600b5a022ffc06516b8b30",
+ [
+ "html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.worker.html",
+ {}
+ ]
+ ],
"2d.text.measure.selection-rects-baselines.tentative.html": [
- "818a63ef65f77489122c2958254ef66c5aa07ac3",
+ "23220d4a4469720844fd5671b58e6ea087029247",
[
null,
{}
]
],
"2d.text.measure.selection-rects-baselines.tentative.worker.js": [
- "56a8d2799a0afbf302eb5e5700fa353e0f455b66",
+ "6d275c042ebff500c07ff4de8d701444abbb62ea",
[
"html/canvas/offscreen/text/2d.text.measure.selection-rects-baselines.tentative.worker.html",
{}
]
],
"2d.text.measure.selection-rects-exceptions.tentative.html": [
- "06ffeacd0370d2f6fe8da9a2624d71e969e0510a",
+ "518fbc654aa4c4c84aa6086b57bce75391100d42",
[
null,
{}
]
],
"2d.text.measure.selection-rects-exceptions.tentative.worker.js": [
- "1ec8bc4973150e8e7cb3840ff74fca49082f3c72",
+ "a3fefb3b2535b647ea8c7aeaf7e36704e1d60cc8",
[
"html/canvas/offscreen/text/2d.text.measure.selection-rects-exceptions.tentative.worker.html",
{}
]
],
"2d.text.measure.selection-rects.tentative.html": [
- "e8574850fac988e14ae813eb258d885b21f9e4f1",
+ "6c0261e15341569c0ed05f6ef0bd35efec1995c0",
[
null,
{}
@@ -660582,6 +661277,17 @@
}
]
]
+ },
+ "tentative": {
+ "access-to-noopener-page-from-no-coop-ro.https.html": [
+ "5e407de34822baea56fb889099683aef49a73cd0",
+ [
+ null,
+ {
+ "timeout": "long"
+ }
+ ]
+ ]
}
},
"resource-popup.https.html": [
@@ -660592,6 +661298,26 @@
]
],
"tentative": {
+ "noopener": {
+ "coop-noopener-allow-popups-restrict-properties.https.html": [
+ "3058011c08870cf6650a68bd27a855f01d51b8b3",
+ [
+ null,
+ {
+ "timeout": "long"
+ }
+ ]
+ ],
+ "coop-noopener-allow-popups.https.html": [
+ "0daf3278476c11d99694c06535289b2d0a92cdf6",
+ [
+ null,
+ {
+ "timeout": "long"
+ }
+ ]
+ ]
+ },
"restrict-properties": {
"access-reporting-closed.https.html": [
"1c315b35d7fa81188aabf59c1c3196a5d5d2a2ea",
@@ -661404,6 +662130,13 @@
]
},
"global-attributes": {
+ "cdata-dir_auto.html": [
+ "91231f7e6c6e6a7cc11856ac6177dd77dfd0fba5",
+ [
+ null,
+ {}
+ ]
+ ],
"classlist-nonstring.html": [
"044f5e8b1b29e409e35f8d8fe5a9bec54914fc96",
[
@@ -661475,7 +662208,7 @@
]
],
"dir-assorted.window.js": [
- "93f798e6002524ebdd89bcc24ab02fa22985ff6f",
+ "3bd63e2ae0518ff95c7e2ff3483bc683d2940c65",
[
"html/dom/elements/global-attributes/dir-assorted.window.html",
{}
@@ -661489,10 +662222,17 @@
]
],
"dir-auto-dynamic-changes.window.js": [
- "a0cf4aae7bad32420b0eb72dbf5c8310d2cce9d4",
+ "d57d8664dfaff8c867c322c5fb4516bcbf6c3a0d",
[
"html/dom/elements/global-attributes/dir-auto-dynamic-changes.window.html",
- {}
+ {
+ "script_metadata": [
+ [
+ "script",
+ "dir-shadow-utils.js"
+ ]
+ ]
+ }
]
],
"dir-auto-form-associated.window.js": [
@@ -661510,7 +662250,7 @@
]
],
"dir-slots-directionality.html": [
- "db783fc55ca3493e75d10f3e8f6d400e5187e531",
+ "c0a11ba9a90949b5046339a956bf8f14d934395e",
[
null,
{}
@@ -672429,10 +673169,12 @@
]
],
"form-validation-reportValidity.html": [
- "c68e21c9d5f7eed6fc3d048c68817fcc6975da36",
+ "5f0393f9275358b1699c562e1fa42153377617bb",
[
null,
- {}
+ {
+ "timeout": "long"
+ }
]
],
"form-validation-validate.html": [
@@ -675249,7 +675991,7 @@
]
],
"name-attribute.html": [
- "2685546e9b00cf39f25fbb790c3543028274b598",
+ "19d62de66f472b641a4bd6b8d66024078d934e51",
[
null,
{}
@@ -675801,7 +676543,7 @@
],
"invokers": {
"idlharness.tentative.html": [
- "8a86a5aaa1811b555f8e90f4df17a7a79afe2d67",
+ "e52ab063f841ff866ebaf01023a4b3e989ee9191",
[
null,
{}
@@ -675878,14 +676620,14 @@
]
],
"invokeelement-interface.tentative.html": [
- "5adacadabb4dcec47019db54b69efaa87f589968",
+ "6821adf71fee0c0e84376d8c02f34d5556177c06",
[
null,
{}
]
],
"invokeevent-dispatch-shadow.tentative.html": [
- "1ecff887608842ba9690beb02a4cb32bdfc593fe",
+ "fb2a113994f76270248ee106e2edd3c4727c9d1d",
[
null,
{
@@ -675894,7 +676636,7 @@
]
],
"invokeevent-interface.tentative.html": [
- "0cfb4d5ee5f59e8264680d6f7943dd956330df05",
+ "500c05f88a106a85f0aa43f24afe78b6ebfcc89a",
[
null,
{
@@ -675903,7 +676645,7 @@
]
],
"invoketarget-button-event-dispatch.tentative.html": [
- "9120cc31926db0f1ada638434895555ab614fc65",
+ "c5dfe14f90b7f214299c4b41f12bd0dbc6f0537f",
[
null,
{
@@ -675913,7 +676655,7 @@
]
],
"invoketarget-fullscreen-behavior.tentative.html": [
- "2e2c5f683f0e841ac13cf985462a93bf97cb7abb",
+ "1210b8637deef9140ff27c20ca99e434358c8f93",
[
null,
{
@@ -675923,7 +676665,7 @@
]
],
"invoketarget-on-audio-behavior.tentative.html": [
- "37acb7a5396b11c8860bca1ad3c85a7b40d3f272",
+ "309a91e2842dd17fee69746e949e91634feaf0fa",
[
null,
{
@@ -675933,7 +676675,7 @@
]
],
"invoketarget-on-audio-invalid-behavior.tentative.html": [
- "9e15ce38e859e09a4dae5c84afff397a03d428d4",
+ "3e18478a52f31db16fb096fb0745017beab70815",
[
null,
{
@@ -675943,7 +676685,7 @@
]
],
"invoketarget-on-details-behavior.tentative.html": [
- "ad9b6caa57dcb2b19765a66e15c9043f620b4d00",
+ "4c4998cf9d860d4be9e325ff08631ff7dad314eb",
[
null,
{
@@ -675953,7 +676695,7 @@
]
],
"invoketarget-on-details-invalid-behavior.tentative.html": [
- "d5e90af9c0e43246c468025b12ef0e8cec115aeb",
+ "3a4e86e9f2c901254942bba5d4ea302389151a0a",
[
null,
{
@@ -675963,7 +676705,7 @@
]
],
"invoketarget-on-dialog-behavior.tentative.html": [
- "9f73e092b0342d461b4b25657c9f795294fad4b3",
+ "9cf1e530b3d17690039c8cb05e4ff878ef48b4a5",
[
null,
{
@@ -675973,7 +676715,7 @@
]
],
"invoketarget-on-dialog-invalid-behavior.tentative.html": [
- "af84c22594a47e311d76485144ce3ed2c8a94b34",
+ "8ea5066dd7449f084202f26c8bb2442fff5617f5",
[
null,
{
@@ -675983,7 +676725,7 @@
]
],
"invoketarget-on-input-number.tentative.html": [
- "b06053b9f1f5a9868036f1a6ceaf5e8281da5556",
+ "643423131eba0a20147557626c2f32ff0d7ffda7",
[
null,
{
@@ -675992,7 +676734,7 @@
]
],
"invoketarget-on-popover-behavior.tentative.html": [
- "f414559e55ae9b606e9ead2c14a651eb7510d1e3",
+ "c974b6ff108b4ce08becd41a840bb3bc17022cfb",
[
null,
{
@@ -676002,7 +676744,7 @@
]
],
"invoketarget-on-popover-invalid-behavior.tentative.html": [
- "755f3a67770fd81abbdccc9ab2efc0762c1b02cc",
+ "31442261f37de3b86c70cd5bed825322b88a1677",
[
null,
{
@@ -676012,7 +676754,7 @@
]
],
"invoketarget-on-video-behavior.tentative.html": [
- "d15d6f95841b7be2ab9bb9e5bd4e4ccf0d634cbd",
+ "e395281ee31fd84db8885444c9ee04cdb118660b",
[
null,
{
@@ -676373,7 +677115,7 @@
]
],
"popover-attribute-basic.html": [
- "2af3bbc137f1a0883921fb025ac548f1a0ec740c",
+ "f13a05288c3e03acf219810022f623ba27793998",
[
null,
{
@@ -689872,6 +690614,13 @@
{}
]
],
+ "box-reflect.html": [
+ "41e072a587c9502c6ced30e80921ef6538a562c0",
+ [
+ null,
+ {}
+ ]
+ ],
"box-shadow.html": [
"765fa8b2d5e0846a498b04cd4c3bb0e52945f860",
[
@@ -692904,6 +693653,26 @@
}
]
],
+ "loaf-pointer-without-render-iframe.html": [
+ "1fb106403a10b0ca0c7f25ae19fd04a19000b710",
+ [
+ null,
+ {
+ "testdriver": true,
+ "timeout": "long"
+ }
+ ]
+ ],
+ "loaf-pointer-without-render.html": [
+ "f05cb218ba691ad4695af633b900dbbfe980f244",
+ [
+ null,
+ {
+ "testdriver": true,
+ "timeout": "long"
+ }
+ ]
+ ],
"loaf-popup.html": [
"3f9758953e96f8e7151bef1d11d05d62e1b59172",
[
@@ -693482,7 +694251,7 @@
]
],
"mrow-fallback.html": [
- "3f9d466148c66877e9fe4104f00d2963cbec0380",
+ "a7e984328c52616c2dfda505afd23bc27503c923",
[
null,
{}
@@ -706002,7 +706771,7 @@
]
],
"payment-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html": [
- "b95deed7cef5ae9a82286543e6c1911c7bdaea4f",
+ "57ae4fbee45f99803ac186cd04c5e07f48ecc8fe",
[
null,
{}
@@ -723254,7 +724023,7 @@
]
],
"sanitizer-names.https.html": [
- "cd33bbc7635322c9933186662da1ace396ad325c",
+ "78f1d605a19e02fac1e760f582d80a57e7ddb0db",
[
null,
{}
@@ -727169,7 +727938,7 @@
]
],
"onselectionchange-on-document.html": [
- "4e0616537771ef3763f9c1b4950964d81cb1b9f5",
+ "af522c3f79c96b53547ee9001b700c45a444119d",
[
null,
{}
@@ -731953,7 +732722,7 @@
]
],
"selection-direction.tentative.html": [
- "6ec9b1934f989e289aa6b746b605456fa1929d11",
+ "340711a8f0682d2736fa355efadab337189a6feb",
[
null,
{
@@ -737951,10 +738720,10 @@
{}
]
],
- "storage-access-beyond-cookies.BroadcastChannel.tentative.sub.https.window.js": [
+ "storage-access-beyond-cookies.BroadcastChannel.sub.https.window.js": [
"feb268b4b8146290eec743779c4a8d9567bc74b3",
[
- "storage-access-api/storage-access-beyond-cookies.BroadcastChannel.tentative.sub.https.window.html",
+ "storage-access-api/storage-access-beyond-cookies.BroadcastChannel.sub.https.window.html",
{
"script_metadata": [
[
@@ -737969,10 +738738,10 @@
}
]
],
- "storage-access-beyond-cookies.SharedWorker.tentative.sub.https.window.js": [
- "613a47ba1b7c5cdcfa7db7531e921ad72a9a8501",
+ "storage-access-beyond-cookies.SharedWorker.sub.https.window.js": [
+ "e05c2ef38d171db26628317018312d3f2f46d7d2",
[
- "storage-access-api/storage-access-beyond-cookies.SharedWorker.tentative.sub.https.window.html",
+ "storage-access-api/storage-access-beyond-cookies.SharedWorker.sub.https.window.html",
{
"script_metadata": [
[
@@ -737987,10 +738756,10 @@
}
]
],
- "storage-access-beyond-cookies.blobStorage.tentative.sub.https.window.js": [
+ "storage-access-beyond-cookies.blobStorage.sub.https.window.js": [
"cc2785b6fac73ffbd3a18c8bd23228a1c8130e9c",
[
- "storage-access-api/storage-access-beyond-cookies.blobStorage.tentative.sub.https.window.html",
+ "storage-access-api/storage-access-beyond-cookies.blobStorage.sub.https.window.html",
{
"script_metadata": [
[
@@ -738005,10 +738774,10 @@
}
]
],
- "storage-access-beyond-cookies.caches.tentative.sub.https.window.js": [
+ "storage-access-beyond-cookies.caches.sub.https.window.js": [
"51e5c648a64cd9f7e629c1d5fe9d8c471d6513d9",
[
- "storage-access-api/storage-access-beyond-cookies.caches.tentative.sub.https.window.html",
+ "storage-access-api/storage-access-beyond-cookies.caches.sub.https.window.html",
{
"script_metadata": [
[
@@ -738023,10 +738792,10 @@
}
]
],
- "storage-access-beyond-cookies.cookies.tentative.sub.https.window.js": [
+ "storage-access-beyond-cookies.cookies.sub.https.window.js": [
"ad760cfda7d8e442da3df71db0154627fdbe4b02",
[
- "storage-access-api/storage-access-beyond-cookies.cookies.tentative.sub.https.window.html",
+ "storage-access-api/storage-access-beyond-cookies.cookies.sub.https.window.html",
{
"script_metadata": [
[
@@ -738041,10 +738810,10 @@
}
]
],
- "storage-access-beyond-cookies.estimate.tentative.sub.https.window.js": [
+ "storage-access-beyond-cookies.estimate.sub.https.window.js": [
"eb8cb0c68d0ad83e526198545fd1bb31070366b3",
[
- "storage-access-api/storage-access-beyond-cookies.estimate.tentative.sub.https.window.html",
+ "storage-access-api/storage-access-beyond-cookies.estimate.sub.https.window.html",
{
"script_metadata": [
[
@@ -738059,10 +738828,10 @@
}
]
],
- "storage-access-beyond-cookies.getDirectory.tentative.sub.https.window.js": [
+ "storage-access-beyond-cookies.getDirectory.sub.https.window.js": [
"d59caa93cd63b3edc8f107ed1fa9ba766db3d287",
[
- "storage-access-api/storage-access-beyond-cookies.getDirectory.tentative.sub.https.window.html",
+ "storage-access-api/storage-access-beyond-cookies.getDirectory.sub.https.window.html",
{
"script_metadata": [
[
@@ -738077,10 +738846,10 @@
}
]
],
- "storage-access-beyond-cookies.indexedDB.tentative.sub.https.window.js": [
+ "storage-access-beyond-cookies.indexedDB.sub.https.window.js": [
"8e9420da0da652f4bade46c9deedd66af8decf3c",
[
- "storage-access-api/storage-access-beyond-cookies.indexedDB.tentative.sub.https.window.html",
+ "storage-access-api/storage-access-beyond-cookies.indexedDB.sub.https.window.html",
{
"script_metadata": [
[
@@ -738095,10 +738864,10 @@
}
]
],
- "storage-access-beyond-cookies.localStorage.tentative.sub.https.window.js": [
+ "storage-access-beyond-cookies.localStorage.sub.https.window.js": [
"80021317790587e8da7fea06324cc03a1504a8ce",
[
- "storage-access-api/storage-access-beyond-cookies.localStorage.tentative.sub.https.window.html",
+ "storage-access-api/storage-access-beyond-cookies.localStorage.sub.https.window.html",
{
"script_metadata": [
[
@@ -738113,10 +738882,10 @@
}
]
],
- "storage-access-beyond-cookies.locks.tentative.sub.https.window.js": [
+ "storage-access-beyond-cookies.locks.sub.https.window.js": [
"ed7d6ea48479a1b2a433db5f438ab001bae93598",
[
- "storage-access-api/storage-access-beyond-cookies.locks.tentative.sub.https.window.html",
+ "storage-access-api/storage-access-beyond-cookies.locks.sub.https.window.html",
{
"script_metadata": [
[
@@ -738131,10 +738900,10 @@
}
]
],
- "storage-access-beyond-cookies.none.tentative.sub.https.window.js": [
+ "storage-access-beyond-cookies.none.sub.https.window.js": [
"ba5ea3279dffe0253f29293536ce2b408a4fe72a",
[
- "storage-access-api/storage-access-beyond-cookies.none.tentative.sub.https.window.html",
+ "storage-access-api/storage-access-beyond-cookies.none.sub.https.window.html",
{
"script_metadata": [
[
@@ -738149,10 +738918,10 @@
}
]
],
- "storage-access-beyond-cookies.sessionStorage.tentative.sub.https.window.js": [
+ "storage-access-beyond-cookies.sessionStorage.sub.https.window.js": [
"93b243f6c1104401f05aff207acdf16186b97899",
[
- "storage-access-api/storage-access-beyond-cookies.sessionStorage.tentative.sub.https.window.html",
+ "storage-access-api/storage-access-beyond-cookies.sessionStorage.sub.https.window.html",
{
"script_metadata": [
[
@@ -738167,10 +738936,10 @@
}
]
],
- "storage-access-beyond-cookies.unpartitioned.tentative.sub.https.window.js": [
+ "storage-access-beyond-cookies.unpartitioned.sub.https.window.js": [
"ddc5b49f4819fc127a99d30ebc2792244cae3570",
[
- "storage-access-api/storage-access-beyond-cookies.unpartitioned.tentative.sub.https.window.html",
+ "storage-access-api/storage-access-beyond-cookies.unpartitioned.sub.https.window.html",
{
"script_metadata": [
[
@@ -746849,14 +747618,7 @@
]
],
"vector-effect-invalid.html": [
- "ec49b88c59959ce33b6b3914026b25003cf74ee8",
- [
- null,
- {}
- ]
- ],
- "vector-effect-valid.html": [
- "9563db0523ba741a94887c9e98d1db37b6da327b",
+ "18f82502ddd3369d477fba0a7b5f96b3c77150ff",
[
null,
{}
@@ -763161,7 +763923,7 @@
]
],
"audio-encoder-config.https.any.js": [
- "a70bf28f80c9ab576a569d9a9798d727133150d5",
+ "ad5d56ca492a6cdf4a0be8338c30647db9e616c6",
[
"webcodecs/audio-encoder-config.https.any.html",
{
@@ -763844,7 +764606,7 @@
]
],
"full-cycle-test.https.any.js": [
- "8e1ae65bdcad6427cfc5901c9411ddca9568deb0",
+ "a63edd04d75ea1c3921456a7179ade7cf39a8375",
[
"webcodecs/full-cycle-test.https.any.html?av1",
{
@@ -766465,7 +767227,7 @@
]
],
"video-encoder-config.https.any.js": [
- "8d554e4f4dbb935d12a6c7e3aa606fc50db01424",
+ "d61958919d78b9e246a7021fa9c19242cb6d7538",
[
"webcodecs/video-encoder-config.https.any.html",
{
@@ -767850,7 +768612,7 @@
]
],
"videoFrame-copyTo-rgb.any.js": [
- "146b6756cb467335087dd234e63b4e9afcd03adb",
+ "eb7351e048b2aff4cff3f2df793ef4741fb1db8d",
[
"webcodecs/videoFrame-copyTo-rgb.any.html",
{
@@ -781432,7 +782194,7 @@
]
],
"convTranspose2d.https.any.js": [
- "c4717a7c538779271f7f806d9b286c18dece87ea",
+ "f7cf4b3200ca280688eb4a244a75b620aebc5c4d",
[
"webnn/validation_tests/convTranspose2d.https.any.html",
{
@@ -781801,7 +782563,7 @@
]
],
"gru.https.any.js": [
- "b1b576e96d070387e36f609f360122bd81f8e10a",
+ "2b85ce23144ca9c7bd7d65975cfb8c230d1cb071",
[
"webnn/validation_tests/gru.https.any.html",
{
@@ -781842,7 +782604,7 @@
]
],
"gruCell.https.any.js": [
- "0465928d514fab705c710707fb89463d8ea100e1",
+ "e37c9ec5209912078b6238d0c544778740e02e83",
[
"webnn/validation_tests/gruCell.https.any.html",
{
@@ -782170,7 +782932,7 @@
]
],
"lstm.https.any.js": [
- "2a3eaa1c20258b9e2e6177c34c651d41e11e21a5",
+ "06834b3960437d821fcdd5e35551ba6dd7608e4a",
[
"webnn/validation_tests/lstm.https.any.html",
{
@@ -782211,7 +782973,7 @@
]
],
"lstmCell.https.any.js": [
- "d4031b07b24e61054828cd3ff84ceead68ad12bb",
+ "6b7bd1958b323980876269c28bfdfd96610ee8bd",
[
"webnn/validation_tests/lstmCell.https.any.html",
{
@@ -782393,7 +783155,7 @@
]
],
"pooling.https.any.js": [
- "08a78f25bafe92803968a88ea07e174d3cafbd78",
+ "6d21f3d52f1c3402faf7b860ba960144fce53121",
[
"webnn/validation_tests/pooling.https.any.html",
{
@@ -782557,7 +783319,7 @@
]
],
"resample2d.https.any.js": [
- "cc52cf97f4ff571cd9a6d7223fbece79a8c8cc3e",
+ "0c6a475e878486453722a2a9e7dcdac0bb177424",
[
"webnn/validation_tests/resample2d.https.any.html",
{
@@ -782844,7 +783606,7 @@
]
],
"split.https.any.js": [
- "6f7809744a7b6ecdceda9a9b97e50176c5b1e884",
+ "91d00b0a6d71ff5e92ac1fac9c38586d665ebe39",
[
"webnn/validation_tests/split.https.any.html",
{
@@ -783008,7 +783770,7 @@
]
],
"where.https.any.js": [
- "a26fa249315d3bdda9dbdc7e1c5e623135aa9c0b",
+ "33394f863227ccc906a16e3175285a237d68e023",
[
"webnn/validation_tests/where.https.any.html",
{
@@ -830139,7 +830901,7 @@
]
],
"prompt_unload.py": [
- "2dc241ba396fd5988c6c95f36afedcab950077b6",
+ "e7d88ad272d0ae0a3cabef1bfb2b675475e70698",
[
null,
{}
@@ -830279,7 +831041,7 @@
},
"handle_user_prompt": {
"handle_user_prompt.py": [
- "5bbc616960fa0461e9cf14ceca918217f633b381",
+ "9044f220bcc8d242a15b415a76882f102ee9d9dc",
[
null,
{}
@@ -830569,7 +831331,7 @@
},
"user_prompt_closed": {
"beforeunload.py": [
- "6b2e788a05879aa6a2326196f415701d7449573d",
+ "ab16526fdb177417269ff93c2e3d8abfdf730e56",
[
null,
{}
@@ -830599,7 +831361,7 @@
]
],
"user_prompt_opened.py": [
- "3a995b150388909587ee8ed834030574de1e4003",
+ "c24128004f73a8b09a61991837b1dad103f91789",
[
null,
{}
@@ -830811,7 +831573,7 @@
},
"integration": {
"cookies_with_network_events.py": [
- "16ca358e23be51d2de3047fd28ec47b66ece4163",
+ "30ba8e3bd7b8009070c4a6c1739b43efc8235f50",
[
null,
{}
@@ -831179,23 +831941,23 @@
]
]
},
- "set_cache_bypass": {
- "contexts_tentative.py": [
- "946d6d9a7eb4f2129de13bf3954b2939775943c9",
+ "set_cache_behavior": {
+ "contexts.py": [
+ "5872a7d12e0d63787616ac4275f2489e91ff570b",
[
null,
{}
]
],
- "invalid_tentative.py": [
- "678d5d313f899718af2b52f24479b57b839ec216",
+ "invalid.py": [
+ "921db569d4f7f7f877116171a8f8753f84d53b24",
[
null,
{}
]
],
- "set_cache_bypass_tentative.py": [
- "42166ef253bb954a7df3099287caed259f33a0ec",
+ "set_cache_behavior.py": [
+ "0d55d520c9636a688f32158a7d7e3fb48e599153",
[
null,
{}
@@ -832908,7 +833670,7 @@
]
],
"unhandled_prompt_behavior.py": [
- "2df69b131ffae686fe2d9b49890e692f3ac8601d",
+ "05b0f9309162b0a35bba1200713bc0db14acee4f",
[
null,
{
diff --git a/tests/wpt/meta/css/css-animations/display-none-prevents-starting-in-subtree.html.ini b/tests/wpt/meta/css/css-animations/display-none-prevents-starting-in-subtree.html.ini
new file mode 100644
index 00000000000..612a75448bb
--- /dev/null
+++ b/tests/wpt/meta/css/css-animations/display-none-prevents-starting-in-subtree.html.ini
@@ -0,0 +1,3 @@
+[display-none-prevents-starting-in-subtree.html]
+ [Elements in a "display: none" tree cannot start CSS Animations.]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-color/animation/opacity-animation-ending-correctly-002.html.ini b/tests/wpt/meta/css/css-color/animation/opacity-animation-ending-correctly-002.html.ini
deleted file mode 100644
index eb3cf41a070..00000000000
--- a/tests/wpt/meta/css/css-color/animation/opacity-animation-ending-correctly-002.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[opacity-animation-ending-correctly-002.html]
- expected: TIMEOUT
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-022.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-022.tentative.html.ini
new file mode 100644
index 00000000000..b9715bdcbcf
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-022.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-022.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-023.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-023.tentative.html.ini
new file mode 100644
index 00000000000..4de92d88f8a
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-023.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-023.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-024.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-024.tentative.html.ini
new file mode 100644
index 00000000000..b4f17815816
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-024.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-024.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-025.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-025.tentative.html.ini
new file mode 100644
index 00000000000..21c21386446
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-025.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-025.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-026.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-026.tentative.html.ini
new file mode 100644
index 00000000000..cc760d52775
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-026.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-026.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-027.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-027.tentative.html.ini
new file mode 100644
index 00000000000..72c0f91de75
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-027.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-027.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-017.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-017.tentative.html.ini
new file mode 100644
index 00000000000..50a860f5cff
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-017.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-017.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-018.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-018.tentative.html.ini
new file mode 100644
index 00000000000..cc12506ab76
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-018.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-018.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-019.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-019.tentative.html.ini
new file mode 100644
index 00000000000..0eb22442455
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-019.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-019.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-020.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-020.tentative.html.ini
new file mode 100644
index 00000000000..c4a3171091e
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-020.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-020.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-021.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-021.tentative.html.ini
new file mode 100644
index 00000000000..ec0dd2607ab
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-021.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-021.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-022.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-022.tentative.html.ini
new file mode 100644
index 00000000000..a4378bd91ec
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-022.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-022.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-023.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-023.tentative.html.ini
new file mode 100644
index 00000000000..be570e7abe9
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-023.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-023.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-024.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-024.tentative.html.ini
new file mode 100644
index 00000000000..cfa4a650a3a
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-024.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-024.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-025.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-025.tentative.html.ini
new file mode 100644
index 00000000000..c1786082c26
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-025.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-025.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-026.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-026.tentative.html.ini
new file mode 100644
index 00000000000..0ee497e3bcd
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-026.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-026.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-027.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-027.tentative.html.ini
new file mode 100644
index 00000000000..30b109549c3
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-027.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-027.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-028.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-028.tentative.html.ini
new file mode 100644
index 00000000000..465e96a5404
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-028.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-028.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-029.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-029.tentative.html.ini
new file mode 100644
index 00000000000..3f6611a7627
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-029.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-029.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-030.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-030.tentative.html.ini
new file mode 100644
index 00000000000..4fdd099a51a
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-030.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-030.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-031.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-031.tentative.html.ini
new file mode 100644
index 00000000000..2c233b1cacc
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-031.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-031.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-032.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-032.tentative.html.ini
new file mode 100644
index 00000000000..00093f16b28
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-auto-032.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-auto-032.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-with-abspos-012.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-with-abspos-012.tentative.html.ini
new file mode 100644
index 00000000000..2f746ad56f3
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-with-abspos-012.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-with-abspos-012.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-with-abspos-013.tentative.html.ini b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-with-abspos-013.tentative.html.ini
new file mode 100644
index 00000000000..f710874fa75
--- /dev/null
+++ b/tests/wpt/meta/css/css-overflow/line-clamp/line-clamp-with-abspos-013.tentative.html.ini
@@ -0,0 +1,2 @@
+[line-clamp-with-abspos-013.tentative.html]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-transforms/transform-input-018.html.ini b/tests/wpt/meta/css/css-transforms/transform-input-018.html.ini
deleted file mode 100644
index f425dbe5f4a..00000000000
--- a/tests/wpt/meta/css/css-transforms/transform-input-018.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[transform-input-018.html]
- expected: FAIL
diff --git a/tests/wpt/meta/css/css-transitions/pseudo-element-transform.html.ini b/tests/wpt/meta/css/css-transitions/pseudo-element-transform.html.ini
new file mode 100644
index 00000000000..67df2295e2e
--- /dev/null
+++ b/tests/wpt/meta/css/css-transitions/pseudo-element-transform.html.ini
@@ -0,0 +1,2 @@
+[pseudo-element-transform.html]
+ expected: TIMEOUT
diff --git a/tests/wpt/meta/css/css-transitions/transitions-retarget.html.ini b/tests/wpt/meta/css/css-transitions/transitions-retarget.html.ini
new file mode 100644
index 00000000000..72d9f209146
--- /dev/null
+++ b/tests/wpt/meta/css/css-transitions/transitions-retarget.html.ini
@@ -0,0 +1,3 @@
+[transitions-retarget.html]
+ [Retargeting a transition should cause the old transition to be cancelled]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-values/calc-size/animation/interpolate-size-interpolation.html.ini b/tests/wpt/meta/css/css-values/calc-size/animation/interpolate-size-interpolation.html.ini
new file mode 100644
index 00000000000..3c23c23b88a
--- /dev/null
+++ b/tests/wpt/meta/css/css-values/calc-size/animation/interpolate-size-interpolation.html.ini
@@ -0,0 +1,2 @@
+[interpolate-size-interpolation.html]
+ expected: ERROR
diff --git a/tests/wpt/meta/css/css-values/calc-size/interpolate-size-computed.html.ini b/tests/wpt/meta/css/css-values/calc-size/interpolate-size-computed.html.ini
new file mode 100644
index 00000000000..ae2404f8a21
--- /dev/null
+++ b/tests/wpt/meta/css/css-values/calc-size/interpolate-size-computed.html.ini
@@ -0,0 +1,6 @@
+[interpolate-size-computed.html]
+ [Property interpolate-size value 'numeric-only']
+ expected: FAIL
+
+ [Property interpolate-size value 'allow-keywords']
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-values/calc-size/interpolate-size-parsing.html.ini b/tests/wpt/meta/css/css-values/calc-size/interpolate-size-parsing.html.ini
new file mode 100644
index 00000000000..61bfc890ca0
--- /dev/null
+++ b/tests/wpt/meta/css/css-values/calc-size/interpolate-size-parsing.html.ini
@@ -0,0 +1,6 @@
+[interpolate-size-parsing.html]
+ [e.style['interpolate-size'\] = "numeric-only" should set the property value]
+ expected: FAIL
+
+ [e.style['interpolate-size'\] = "allow-keywords" should set the property value]
+ expected: FAIL
diff --git a/tests/wpt/meta/css/css-values/vh_not_refreshing_on_chrome.html.ini b/tests/wpt/meta/css/css-values/vh_not_refreshing_on_chrome.html.ini
deleted file mode 100644
index 26435e28b09..00000000000
--- a/tests/wpt/meta/css/css-values/vh_not_refreshing_on_chrome.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[vh_not_refreshing_on_chrome.html]
- expected: FAIL
diff --git a/tests/wpt/meta/css/cssom-view/MediaQueryListEvent.html.ini b/tests/wpt/meta/css/cssom-view/MediaQueryListEvent.html.ini
index b7e476a0830..9250d55022a 100644
--- a/tests/wpt/meta/css/cssom-view/MediaQueryListEvent.html.ini
+++ b/tests/wpt/meta/css/cssom-view/MediaQueryListEvent.html.ini
@@ -2,3 +2,6 @@
expected: TIMEOUT
[constructor of "change" event]
expected: NOTRUN
+
+ [argument of onchange]
+ expected: TIMEOUT
diff --git a/tests/wpt/meta/dom/observable/tentative/observable-constructor.any.js.ini b/tests/wpt/meta/dom/observable/tentative/observable-constructor.any.js.ini
index 3997fbb757a..8221c036594 100644
--- a/tests/wpt/meta/dom/observable/tentative/observable-constructor.any.js.ini
+++ b/tests/wpt/meta/dom/observable/tentative/observable-constructor.any.js.ini
@@ -110,6 +110,15 @@
[Subscriber#error() value is stored as Subscriber's AbortSignal's reason]
expected: FAIL
+ [Teardowns are called in upstream->downstream order on consumer-initiated unsubscription]
+ expected: FAIL
+
+ [Teardowns are called in downstream->upstream order on consumer-initiated unsubscription with pre-aborted Signal]
+ expected: FAIL
+
+ [Producer-initiated unsubscription in a downstream Observable fires abort events before each teardown, in downstream->upstream order]
+ expected: FAIL
+
[observable-constructor.any.html]
[Observable constructor]
@@ -222,3 +231,12 @@
[Subscriber#error() value is stored as Subscriber's AbortSignal's reason]
expected: FAIL
+
+ [Teardowns are called in upstream->downstream order on consumer-initiated unsubscription]
+ expected: FAIL
+
+ [Teardowns are called in downstream->upstream order on consumer-initiated unsubscription with pre-aborted Signal]
+ expected: FAIL
+
+ [Producer-initiated unsubscription in a downstream Observable fires abort events before each teardown, in downstream->upstream order]
+ expected: FAIL
diff --git a/tests/wpt/meta/fetch/metadata/generated/element-img-environment-change.sub.html.ini b/tests/wpt/meta/fetch/metadata/generated/element-img-environment-change.sub.html.ini
index 410ec4c1d39..4648085f98d 100644
--- a/tests/wpt/meta/fetch/metadata/generated/element-img-environment-change.sub.html.ini
+++ b/tests/wpt/meta/fetch/metadata/generated/element-img-environment-change.sub.html.ini
@@ -41,3 +41,6 @@
[sec-fetch-site - HTTPS downgrade-upgrade, no attributes]
expected: NOTRUN
+
+ [sec-fetch-site - Not sent to non-trustworthy same-origin destination, no attributes]
+ expected: FAIL
diff --git a/tests/wpt/meta/html/browsers/history/the-history-interface/traverse_the_history_3.html.ini b/tests/wpt/meta/html/browsers/history/the-history-interface/traverse_the_history_3.html.ini
deleted file mode 100644
index a03a8322165..00000000000
--- a/tests/wpt/meta/html/browsers/history/the-history-interface/traverse_the_history_3.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[traverse_the_history_3.html]
- [Multiple history traversals, last would be aborted]
- expected: FAIL
diff --git a/tests/wpt/meta/html/browsers/windows/embedded-opener-remove-frame.html.ini b/tests/wpt/meta/html/browsers/windows/embedded-opener-remove-frame.html.ini
index 91030fe2f7f..c43a5aa58f3 100644
--- a/tests/wpt/meta/html/browsers/windows/embedded-opener-remove-frame.html.ini
+++ b/tests/wpt/meta/html/browsers/windows/embedded-opener-remove-frame.html.ini
@@ -1,5 +1,4 @@
[embedded-opener-remove-frame.html]
- expected: CRASH
[opener of discarded nested browsing context]
expected: FAIL
diff --git a/tests/wpt/meta/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-cross-origin.sub.html.ini b/tests/wpt/meta/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-cross-origin.sub.html.ini
deleted file mode 100644
index 283c44174a7..00000000000
--- a/tests/wpt/meta/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-cross-origin.sub.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[first-party-to-third-party-cross-partition-cross-origin.sub.html]
- expected: TIMEOUT
- [postMessage: First-Party to Third-Party, Cross-Partition, Cross-Origin]
- expected: TIMEOUT
diff --git a/tests/wpt/meta/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-same-origin.sub.html.ini b/tests/wpt/meta/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-same-origin.sub.html.ini
deleted file mode 100644
index 762a134ed26..00000000000
--- a/tests/wpt/meta/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-same-origin.sub.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[first-party-to-third-party-cross-partition-same-origin.sub.html]
- expected: TIMEOUT
- [postMessage: First-Party to Third-Party, Cross-Partition, Same-Origin]
- expected: TIMEOUT
diff --git a/tests/wpt/meta/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html.ini b/tests/wpt/meta/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html.ini
deleted file mode 100644
index e211e6be83a..00000000000
--- a/tests/wpt/meta/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[third-party-to-third-party-cross-partition-cross-origin.sub.html]
- expected: TIMEOUT
- [postMessage: Third-Party to Third-Party, Cross-Partition, Cross-Origin]
- expected: TIMEOUT
diff --git a/tests/wpt/meta/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html.ini b/tests/wpt/meta/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html.ini
deleted file mode 100644
index 488c6bd15d4..00000000000
--- a/tests/wpt/meta/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[third-party-to-third-party-cross-partition-same-origin.sub.html]
- expected: TIMEOUT
- [postMessage: Third-Party to Third-Party, Cross-Partition, Same-Origin]
- expected: TIMEOUT
diff --git a/tests/wpt/meta/html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html.ini b/tests/wpt/meta/html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html.ini
deleted file mode 100644
index 30607f12f29..00000000000
--- a/tests/wpt/meta/html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[third-party-to-third-party-same-partition.sub.html]
- expected: TIMEOUT
- [postMessage: Third-Party to Third-Party, Same-Partition]
- expected: TIMEOUT
diff --git a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-drawImage.html.ini b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-drawImage.html.ini
index 6782931abac..c94490ae5ee 100644
--- a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-drawImage.html.ini
+++ b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-drawImage.html.ini
@@ -164,4 +164,3 @@
[createImageBitmap from a vector HTMLImageElement scaled up, and drawImage on the created ImageBitmap]
expected: FAIL
-
diff --git a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-invalid-args.html.ini b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-invalid-args.html.ini
index 74a99072845..2a3701b5bd0 100644
--- a/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-invalid-args.html.ini
+++ b/tests/wpt/meta/html/canvas/element/manual/imagebitmap/createImageBitmap-invalid-args.html.ini
@@ -1,5 +1,5 @@
[createImageBitmap-invalid-args.html]
- expected: ERROR
+ expected: TIMEOUT
[createImageBitmap with a vector HTMLImageElement source and sw set to 0]
expected: FAIL
diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.measure.caret-position-edge-cases.tentative.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.measure.caret-position-edge-cases.tentative.html.ini
new file mode 100644
index 00000000000..751ca58fa7e
--- /dev/null
+++ b/tests/wpt/meta/html/canvas/element/text/2d.text.measure.caret-position-edge-cases.tentative.html.ini
@@ -0,0 +1,3 @@
+[2d.text.measure.caret-position-edge-cases.tentative.html]
+ [Test the edge cases for caretPositionFromPoint, where the point is at the edge of glyph and at the midpoint.]
+ expected: FAIL
diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.measure.caret-position-edges.tentative.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.measure.caret-position-edges.tentative.html.ini
new file mode 100644
index 00000000000..a8f84833ae9
--- /dev/null
+++ b/tests/wpt/meta/html/canvas/element/text/2d.text.measure.caret-position-edges.tentative.html.ini
@@ -0,0 +1,30 @@
+[2d.text.measure.caret-position-edges.tentative.html]
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align left.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align left.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align center.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align center.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align right.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align right.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align start.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align start.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align end.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align end.]
+ expected: FAIL
diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.measure.caret-position.tentative.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.measure.caret-position.tentative.html.ini
new file mode 100644
index 00000000000..2b7dea3b3b2
--- /dev/null
+++ b/tests/wpt/meta/html/canvas/element/text/2d.text.measure.caret-position.tentative.html.ini
@@ -0,0 +1,120 @@
+[2d.text.measure.caret-position.tentative.html]
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 10px letter spacing and directional-override.]
+ expected: FAIL
diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html.ini
new file mode 100644
index 00000000000..26263231bef
--- /dev/null
+++ b/tests/wpt/meta/html/canvas/element/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html.ini
@@ -0,0 +1,2 @@
+[2d.text.measure.getActualBoundingBox-exceptions.tentative.html]
+ expected: ERROR
diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html.ini
new file mode 100644
index 00000000000..174591cf89f
--- /dev/null
+++ b/tests/wpt/meta/html/canvas/element/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html.ini
@@ -0,0 +1,12 @@
+[2d.text.measure.getActualBoundingBox-full-text.tentative.html]
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and no-directional-override]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and no-directional-override]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and directional-override]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and directional-override]
+ expected: FAIL
diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.measure.getActualBoundingBox.tentative.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.measure.getActualBoundingBox.tentative.html.ini
new file mode 100644
index 00000000000..fbde9c92f4b
--- /dev/null
+++ b/tests/wpt/meta/html/canvas/element/text/2d.text.measure.getActualBoundingBox.tentative.html.ini
@@ -0,0 +1,18 @@
+[2d.text.measure.getActualBoundingBox.tentative.html]
+ [Test TextMetrics::getActualBoundingBox(), with text align left , and 0px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align center , and 0px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align right , and 0px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align left , and 10px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align center , and 10px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align right , and 10px letter spacing.]
+ expected: FAIL
diff --git a/tests/wpt/meta/html/canvas/element/text/2d.text.measure.selection-rects.tentative.html.ini b/tests/wpt/meta/html/canvas/element/text/2d.text.measure.selection-rects.tentative.html.ini
index bd83dffdce1..26358feced0 100644
--- a/tests/wpt/meta/html/canvas/element/text/2d.text.measure.selection-rects.tentative.html.ini
+++ b/tests/wpt/meta/html/canvas/element/text/2d.text.measure.selection-rects.tentative.html.ini
@@ -1,3 +1,75 @@
[2d.text.measure.selection-rects.tentative.html]
[Check that TextMetrics::getSelectionRects() matches its DOM equivalent.]
expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 10px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 10px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 10px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 10px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 10px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 10px letter spacing, and directional-override.]
+ expected: FAIL
diff --git a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.html.ini b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.html.ini
new file mode 100644
index 00000000000..751ca58fa7e
--- /dev/null
+++ b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.html.ini
@@ -0,0 +1,3 @@
+[2d.text.measure.caret-position-edge-cases.tentative.html]
+ [Test the edge cases for caretPositionFromPoint, where the point is at the edge of glyph and at the midpoint.]
+ expected: FAIL
diff --git a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.worker.js.ini b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.worker.js.ini
new file mode 100644
index 00000000000..5d4cad8801f
--- /dev/null
+++ b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.worker.js.ini
@@ -0,0 +1,3 @@
+[2d.text.measure.caret-position-edge-cases.tentative.worker.html]
+ [Test the edge cases for caretPositionFromPoint, where the point is at the edge of glyph and at the midpoint.]
+ expected: FAIL
diff --git a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.html.ini b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.html.ini
new file mode 100644
index 00000000000..a8f84833ae9
--- /dev/null
+++ b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.html.ini
@@ -0,0 +1,30 @@
+[2d.text.measure.caret-position-edges.tentative.html]
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align left.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align left.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align center.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align center.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align right.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align right.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align start.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align start.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align end.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align end.]
+ expected: FAIL
diff --git a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.worker.js.ini b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.worker.js.ini
new file mode 100644
index 00000000000..08ebd3f82f7
--- /dev/null
+++ b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.worker.js.ini
@@ -0,0 +1,30 @@
+[2d.text.measure.caret-position-edges.tentative.worker.html]
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align left.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align left.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align center.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align center.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align right.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align right.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align start.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align start.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align end.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align end.]
+ expected: FAIL
diff --git a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.caret-position.tentative.html.ini b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.caret-position.tentative.html.ini
new file mode 100644
index 00000000000..2b7dea3b3b2
--- /dev/null
+++ b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.caret-position.tentative.html.ini
@@ -0,0 +1,120 @@
+[2d.text.measure.caret-position.tentative.html]
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 0px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 10px letter spacing and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 0px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 10px letter spacing and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 10px letter spacing and directional-override.]
+ expected: FAIL
diff --git a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html.ini b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html.ini
new file mode 100644
index 00000000000..26263231bef
--- /dev/null
+++ b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html.ini
@@ -0,0 +1,2 @@
+[2d.text.measure.getActualBoundingBox-exceptions.tentative.html]
+ expected: ERROR
diff --git a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.worker.js.ini b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.worker.js.ini
new file mode 100644
index 00000000000..615275f70fe
--- /dev/null
+++ b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.worker.js.ini
@@ -0,0 +1,2 @@
+[2d.text.measure.getActualBoundingBox-exceptions.tentative.worker.html]
+ expected: TIMEOUT
diff --git a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html.ini b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html.ini
new file mode 100644
index 00000000000..174591cf89f
--- /dev/null
+++ b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html.ini
@@ -0,0 +1,12 @@
+[2d.text.measure.getActualBoundingBox-full-text.tentative.html]
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and no-directional-override]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and no-directional-override]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and directional-override]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and directional-override]
+ expected: FAIL
diff --git a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.worker.js.ini b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.worker.js.ini
new file mode 100644
index 00000000000..2f1f2db1ce6
--- /dev/null
+++ b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.worker.js.ini
@@ -0,0 +1,12 @@
+[2d.text.measure.getActualBoundingBox-full-text.tentative.worker.html]
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and no-directional-override]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and no-directional-override]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and directional-override]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and directional-override]
+ expected: FAIL
diff --git a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.html.ini b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.html.ini
new file mode 100644
index 00000000000..fbde9c92f4b
--- /dev/null
+++ b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.html.ini
@@ -0,0 +1,18 @@
+[2d.text.measure.getActualBoundingBox.tentative.html]
+ [Test TextMetrics::getActualBoundingBox(), with text align left , and 0px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align center , and 0px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align right , and 0px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align left , and 10px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align center , and 10px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align right , and 10px letter spacing.]
+ expected: FAIL
diff --git a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.worker.js.ini b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.worker.js.ini
new file mode 100644
index 00000000000..f3cc13a0b5e
--- /dev/null
+++ b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.worker.js.ini
@@ -0,0 +1,18 @@
+[2d.text.measure.getActualBoundingBox.tentative.worker.html]
+ [Test TextMetrics::getActualBoundingBox(), with text align left , and 0px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align center , and 0px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align right , and 0px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align left , and 10px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align center , and 10px letter spacing.]
+ expected: FAIL
+
+ [Test TextMetrics::getActualBoundingBox(), with text align right , and 10px letter spacing.]
+ expected: FAIL
diff --git a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.selection-rects.tentative.html.ini b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.selection-rects.tentative.html.ini
index bd83dffdce1..26358feced0 100644
--- a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.selection-rects.tentative.html.ini
+++ b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.selection-rects.tentative.html.ini
@@ -1,3 +1,75 @@
[2d.text.measure.selection-rects.tentative.html]
[Check that TextMetrics::getSelectionRects() matches its DOM equivalent.]
expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 0px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 10px letter spacing, and no-directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 0px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 10px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 10px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 10px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 10px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 10px letter spacing, and directional-override.]
+ expected: FAIL
+
+ [Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 10px letter spacing, and directional-override.]
+ expected: FAIL
diff --git a/tests/wpt/meta/html/dom/elements/global-attributes/cdata-dir_auto.html.ini b/tests/wpt/meta/html/dom/elements/global-attributes/cdata-dir_auto.html.ini
new file mode 100644
index 00000000000..e21e72894e5
--- /dev/null
+++ b/tests/wpt/meta/html/dom/elements/global-attributes/cdata-dir_auto.html.ini
@@ -0,0 +1,12 @@
+[cdata-dir_auto.html]
+ [Content of CDATA is ignored for dir=auto in html document]
+ expected: FAIL
+
+ [Text in CDATASection is considered when determining auto directionality]
+ expected: FAIL
+
+ [Directionality is updated when removing CDATASection]
+ expected: FAIL
+
+ [Directionality is updated when changing text of CDATASection]
+ expected: FAIL
diff --git a/tests/wpt/meta/html/dom/elements/global-attributes/dir-assorted.window.js.ini b/tests/wpt/meta/html/dom/elements/global-attributes/dir-assorted.window.js.ini
index 0daeceb99a1..1e6939b0478 100644
--- a/tests/wpt/meta/html/dom/elements/global-attributes/dir-assorted.window.js.ini
+++ b/tests/wpt/meta/html/dom/elements/global-attributes/dir-assorted.window.js.ini
@@ -118,3 +118,6 @@
[non-html textarea element text contents influence dir=auto]
expected: FAIL
+
+ [text changes apply to dir=auto on further ancestor after removing dir=ltr from closer ancestor]
+ expected: FAIL
diff --git a/tests/wpt/meta/html/dom/elements/global-attributes/dir-auto-dynamic-changes.window.js.ini b/tests/wpt/meta/html/dom/elements/global-attributes/dir-auto-dynamic-changes.window.js.ini
index 8becf694cfa..7395ce6f2ed 100644
--- a/tests/wpt/meta/html/dom/elements/global-attributes/dir-auto-dynamic-changes.window.js.ini
+++ b/tests/wpt/meta/html/dom/elements/global-attributes/dir-auto-dynamic-changes.window.js.ini
@@ -22,3 +22,27 @@
[slotted non-HTML elements after dynamically assigning dir=auto, and dir attribute ignored on non-HTML elements]
expected: FAIL
+
+ [dir=auto ancestor considers text in subtree after removing dir=ltr from it]
+ expected: FAIL
+
+ [Slotted content affects multiple dir=auto slots]
+ expected: FAIL
+
+ [Removing slotted content resets direction on dir=auto slot]
+ expected: FAIL
+
+ [Removing child of slotted content changes direction on dir=auto slot]
+ expected: FAIL
+
+ [Child directionality gets updated when dir=auto is set on parent]
+ expected: FAIL
+
+ [dir=auto slot is updated by text in value of input element children]
+ expected: FAIL
+
+ [dir=auto slot is updated if input stops being auto-directionality form-associated]
+ expected: FAIL
+
+ [slot provides updated directionality from host to a dir=auto container]
+ expected: FAIL
diff --git a/tests/wpt/meta/html/dom/elements/global-attributes/dir-slots-directionality.html.ini b/tests/wpt/meta/html/dom/elements/global-attributes/dir-slots-directionality.html.ini
index 2ff8bf31e7e..cd0a57e465c 100644
--- a/tests/wpt/meta/html/dom/elements/global-attributes/dir-slots-directionality.html.ini
+++ b/tests/wpt/meta/html/dom/elements/global-attributes/dir-slots-directionality.html.ini
@@ -25,3 +25,12 @@
[slot with dir attribute is skipped by dir=auto]
expected: FAIL
+
+ [dir=auto slot ignores dir attribute of assigned nodes]
+ expected: FAIL
+
+ [dir=auto slot considers text in bdi assigned nodes]
+ expected: FAIL
+
+ [dir=auto slot considers text in value of input element children]
+ expected: FAIL
diff --git a/tests/wpt/meta/html/semantics/embedded-content/media-elements/playing-the-media-resource/loop-from-ended.tentative.html.ini b/tests/wpt/meta/html/semantics/embedded-content/media-elements/playing-the-media-resource/loop-from-ended.tentative.html.ini
index cf8057c7fa8..6897533d946 100644
--- a/tests/wpt/meta/html/semantics/embedded-content/media-elements/playing-the-media-resource/loop-from-ended.tentative.html.ini
+++ b/tests/wpt/meta/html/semantics/embedded-content/media-elements/playing-the-media-resource/loop-from-ended.tentative.html.ini
@@ -5,4 +5,3 @@
[play() with loop set to true after playback ended]
expected: TIMEOUT
-
diff --git a/tests/wpt/meta/html/semantics/embedded-content/media-elements/track/track-element/no-cuechange-before-play.html.ini b/tests/wpt/meta/html/semantics/embedded-content/media-elements/track/track-element/no-cuechange-before-play.html.ini
index 4caf5931a11..246e55deb7e 100644
--- a/tests/wpt/meta/html/semantics/embedded-content/media-elements/track/track-element/no-cuechange-before-play.html.ini
+++ b/tests/wpt/meta/html/semantics/embedded-content/media-elements/track/track-element/no-cuechange-before-play.html.ini
@@ -1,3 +1,4 @@
[no-cuechange-before-play.html]
+ expected: TIMEOUT
[Ensure that the 'cuechange' event is not fired before video playback has begun.]
- expected: FAIL
+ expected: TIMEOUT
diff --git a/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-1.html.ini b/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-1.html.ini
index d7e7d1b9815..f455bb20528 100644
--- a/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-1.html.ini
+++ b/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-1.html.ini
@@ -1,4 +1,4 @@
[iframe_sandbox_popups_escaping-1.html]
expected: CRASH
[Check that popups from a sandboxed iframe escape the sandbox if\n allow-popups-to-escape-sandbox is used]
- expected: TIMEOUT
+ expected: FAIL
diff --git a/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-1.html.ini b/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-1.html.ini
index 96d866bf3cc..bbc1f35d8d9 100644
--- a/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-1.html.ini
+++ b/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-1.html.ini
@@ -1,4 +1,3 @@
[iframe_sandbox_popups_nonescaping-1.html]
- expected: TIMEOUT
[Check that popups from a sandboxed iframe do not escape the sandbox]
expected: FAIL
diff --git a/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-2.html.ini b/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-2.html.ini
index c6f1e5d7d84..a6591b318dc 100644
--- a/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-2.html.ini
+++ b/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-2.html.ini
@@ -1,4 +1,4 @@
[iframe_sandbox_popups_nonescaping-2.html]
expected: TIMEOUT
[Check that popups from a sandboxed iframe do not escape the sandbox]
- expected: NOTRUN
+ expected: FAIL
diff --git a/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-3.html.ini b/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-3.html.ini
index ccdaf8d61b2..ff6467094b8 100644
--- a/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-3.html.ini
+++ b/tests/wpt/meta/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-3.html.ini
@@ -1,3 +1,3 @@
[iframe_sandbox_popups_nonescaping-3.html]
[Check that popups from a sandboxed iframe do not escape the sandbox]
- expected: NOTRUN
+ expected: FAIL
diff --git a/tests/wpt/meta/html/semantics/forms/form-submission-0/reparent-form-during-planned-navigation-task.html.ini b/tests/wpt/meta/html/semantics/forms/form-submission-0/reparent-form-during-planned-navigation-task.html.ini
deleted file mode 100644
index 7682a4830bf..00000000000
--- a/tests/wpt/meta/html/semantics/forms/form-submission-0/reparent-form-during-planned-navigation-task.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[reparent-form-during-planned-navigation-task.html]
- expected: TIMEOUT
- [reparent-form-during-planned-navigation-task]
- expected: TIMEOUT
diff --git a/tests/wpt/meta/html/semantics/invokers/idlharness.tentative.html.ini b/tests/wpt/meta/html/semantics/invokers/idlharness.tentative.html.ini
index f9d4b78a7c6..cd96793eab1 100644
--- a/tests/wpt/meta/html/semantics/invokers/idlharness.tentative.html.ini
+++ b/tests/wpt/meta/html/semantics/invokers/idlharness.tentative.html.ini
@@ -34,3 +34,39 @@
[InvokeEvent interface: new InvokeEvent("invoke") must inherit property "action" with the proper type]
expected: FAIL
+
+ [CommandEvent interface: existence and properties of interface object]
+ expected: FAIL
+
+ [CommandEvent interface object length]
+ expected: FAIL
+
+ [CommandEvent interface object name]
+ expected: FAIL
+
+ [CommandEvent interface: existence and properties of interface prototype object]
+ expected: FAIL
+
+ [CommandEvent interface: existence and properties of interface prototype object's "constructor" property]
+ expected: FAIL
+
+ [CommandEvent interface: existence and properties of interface prototype object's @@unscopables property]
+ expected: FAIL
+
+ [CommandEvent interface: attribute invoker]
+ expected: FAIL
+
+ [CommandEvent interface: attribute command]
+ expected: FAIL
+
+ [CommandEvent must be primary interface of new CommandEvent("invoke")]
+ expected: FAIL
+
+ [Stringification of new CommandEvent("invoke")]
+ expected: FAIL
+
+ [CommandEvent interface: new CommandEvent("invoke") must inherit property "invoker" with the proper type]
+ expected: FAIL
+
+ [CommandEvent interface: new CommandEvent("invoke") must inherit property "command" with the proper type]
+ expected: FAIL
diff --git a/tests/wpt/meta/html/semantics/invokers/invokeelement-interface.tentative.html.ini b/tests/wpt/meta/html/semantics/invokers/invokeelement-interface.tentative.html.ini
index 587bdaf84d3..d04574db48e 100644
--- a/tests/wpt/meta/html/semantics/invokers/invokeelement-interface.tentative.html.ini
+++ b/tests/wpt/meta/html/semantics/invokers/invokeelement-interface.tentative.html.ini
@@ -43,3 +43,33 @@
[invokeAction reflects '' when attribute set to [\]]
expected: FAIL
+
+ [commandForElement reflects invokee HTML element]
+ expected: FAIL
+
+ [commandForElement reflects set value]
+ expected: FAIL
+
+ [commandForElement reflects set value across shadow root into light dom]
+ expected: FAIL
+
+ [commandForElement does not reflect set value inside shadowroot]
+ expected: FAIL
+
+ [commandForElement throws error on assignment of non Element]
+ expected: FAIL
+
+ [command reflects '' when attribute empty, setAttribute version]
+ expected: FAIL
+
+ [command reflects same casing]
+ expected: FAIL
+
+ [command reflects tostring value]
+ expected: FAIL
+
+ [command reflects '' when attribute set to [\]]
+ expected: FAIL
+
+ [command reflects tostring value 2]
+ expected: FAIL
diff --git a/tests/wpt/meta/resource-timing/test_resource_timing.https.html.ini b/tests/wpt/meta/resource-timing/test_resource_timing.https.html.ini
index dac51c99110..5a2e10ff69c 100644
--- a/tests/wpt/meta/resource-timing/test_resource_timing.https.html.ini
+++ b/tests/wpt/meta/resource-timing/test_resource_timing.https.html.ini
@@ -68,6 +68,3 @@
[PerformanceEntry has correct protocol attribute (xmlhttprequest)]
expected: FAIL
-
- [PerformanceEntry has correct name, initiatorType, startTime, and duration (img)]
- expected: FAIL
diff --git a/tests/wpt/tests/.github/workflows/docker.yml b/tests/wpt/tests/.github/workflows/docker.yml
index 32fb814e6d9..c6cb0ac58ef 100644
--- a/tests/wpt/tests/.github/workflows/docker.yml
+++ b/tests/wpt/tests/.github/workflows/docker.yml
@@ -40,7 +40,7 @@ jobs:
latest
type=raw,value=${{ inputs.tag }}
- name: Build and push the Docker image
- uses: docker/build-push-action@31159d49c0d4756269a0940a750801a1ea5d7003 # v6.1.0
+ uses: docker/build-push-action@15560696de535e4014efeff63c48f16952e52dd1 # v6.2.0
with:
context: ./tools/docker
push: true
diff --git a/tests/wpt/tests/.taskcluster.yml b/tests/wpt/tests/.taskcluster.yml
index 38af7fa10af..85204ee17b9 100644
--- a/tests/wpt/tests/.taskcluster.yml
+++ b/tests/wpt/tests/.taskcluster.yml
@@ -57,7 +57,7 @@ tasks:
owner: ${owner}
source: ${event.repository.clone_url}
payload:
- image: webplatformtests/wpt:0.57
+ image: ghcr.io/web-platform-tests/wpt:1
maxRunTime: 7200
artifacts:
public/results:
diff --git a/tests/wpt/tests/CODEOWNERS b/tests/wpt/tests/CODEOWNERS
index 140e0c6545b..07bda888b20 100644
--- a/tests/wpt/tests/CODEOWNERS
+++ b/tests/wpt/tests/CODEOWNERS
@@ -4,3 +4,7 @@
# Prevent accidentally touching tools/third_party
/tools/third_party/ @web-platform-tests/wpt-core-team
+
+# Require a review for Dockerfile
+tools/docker/Dockerfile @web-platform-tests/wpt-core-team @web-platform-tests/admins
+.taskcluster.yml @web-platform-tests/wpt-core-team @web-platform-tests/admins
diff --git a/tests/wpt/tests/accname/name/comp_tooltip.html b/tests/wpt/tests/accname/name/comp_tooltip.html
index deaabefd93a..c12f9c79fa0 100644
--- a/tests/wpt/tests/accname/name/comp_tooltip.html
+++ b/tests/wpt/tests/accname/name/comp_tooltip.html
@@ -49,7 +49,8 @@
<!-- TODO: Move these: https://github.com/web-platform-tests/interop-accessibility/issues/78 -->
<!-- https://w3c.github.io/html-aam/#text-level-element-accessible-name-computation -->
<abbr title="Web Platform Tests" data-expectedlabel="Web Platform Tests" data-testname="abbr with tooltip label" class="ex">WPT</abbr>
-<kbd title="Control + Option" data-expectedlabel="Control + Option" data-testname="kbd with tooltip label" class="ex">CTRL + OPT</kbd>
+<!-- kbd test disabled: see resolution at https://github.com/web-platform-tests/interop-accessibility/issues/131 -->
+<!-- <kbd title="Control + Option" data-expectedlabel="Control + Option" data-testname="kbd with tooltip label" class="ex">CTRL + OPT</kbd> -->
<!-- TODO: Move these: https://github.com/web-platform-tests/interop-accessibility/issues/78 -->
<!-- https://w3c.github.io/html-aam/#summary-element-accessible-name-computation -->
@@ -66,4 +67,4 @@
AriaUtils.verifyLabelsBySelector(".ex");
</script>
</body>
-</html> \ No newline at end of file
+</html>
diff --git a/tests/wpt/tests/attribution-reporting/aggregatable-debug/simple-trigger-aggregatable-debug-report.sub.https.html b/tests/wpt/tests/attribution-reporting/aggregatable-debug/simple-trigger-aggregatable-debug-report.sub.https.html
index 4dbf7b39a7b..68eae824a29 100644
--- a/tests/wpt/tests/attribution-reporting/aggregatable-debug/simple-trigger-aggregatable-debug-report.sub.https.html
+++ b/tests/wpt/tests/attribution-reporting/aggregatable-debug/simple-trigger-aggregatable-debug-report.sub.https.html
@@ -11,6 +11,7 @@ attribution_reporting_promise_test(async t => {
registerAttributionSrcByImg(createRedirectChain([
{
trigger: {
+ event_trigger_data: [{}],
aggregatable_debug_reporting: {
key_piece: '0x5',
debug_data: [
diff --git a/tests/wpt/tests/credential-management/support/README.md b/tests/wpt/tests/credential-management/support/README.md
index a6d33ff6f75..f58d4cd0ce4 100644
--- a/tests/wpt/tests/credential-management/support/README.md
+++ b/tests/wpt/tests/credential-management/support/README.md
@@ -31,24 +31,3 @@ per engine:
- function receive(): the main/only function that can be mocked
- function expect(): the main/only function that enables us to mock it
- enum State {kSuccess, kTimeout}: allows you to mock success/failures
-
-## FedCM Testing
-
-`fedcm-mojojs-helper.js` exposes `fedcm_mojo_mock_test` which is a specialized
-`promise_test` which comes pre-setup with the appropriate mocking infrastructure
-to emulate platform federated auth backend. The mock is passed to the test
-function as the second parameter.
-
-Example usage:
-```
-<script type="module">
- import {fedcm_mojo_mock_test} from './support/fedcm-mojojs-helper.js';
-
- fedcm_mojo_mock_test(async (t, mock) => {
- mock.returnToken("https://idp.test/fedcm.json", "a_token");
- assert_equals("a_token", await navigator.credentials.get(options));
- }, "Successfully obtaining a token using mock.");
-</script>
-```
-
-The chromium implementation uses the MojoJS shim.
diff --git a/tests/wpt/tests/css/CSS2/floats/crashtests/firefox-bug-1906768.html b/tests/wpt/tests/css/CSS2/floats/crashtests/firefox-bug-1906768.html
new file mode 100644
index 00000000000..0300a90b206
--- /dev/null
+++ b/tests/wpt/tests/css/CSS2/floats/crashtests/firefox-bug-1906768.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<style>
+* {
+ line-break: anywhere;
+ columns: 66 0px;
+}
+#a {
+ float: right;
+}
+</style>
+<marquee>
+<input>
+<optgroup>a</optgroup>
+</marquee>
+<li>bbbbbbbbbbbbbbb</li>
+<li id="a">aaaaaaaaaaaaaaaaaa</li>
diff --git a/tests/wpt/tests/css/css-animations/display-none-prevents-starting-in-subtree.html b/tests/wpt/tests/css/css-animations/display-none-prevents-starting-in-subtree.html
new file mode 100644
index 00000000000..d5d48b2a184
--- /dev/null
+++ b/tests/wpt/tests/css/css-animations/display-none-prevents-starting-in-subtree.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:graouts@webkit.org">
+<link rel=help href="https://drafts.csswg.org/css-animations/#animations">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-animations/support/testcommon.js"></script>
+<style>
+@keyframes margin {
+ 100% { margin-left: 200px }
+}
+
+#child {
+ animation: margin 1s forwards;
+}
+</style>
+<div id="container">
+ <div>
+ <div id="child"></div>
+ </div>
+</div>
+<script>
+test(() => {
+ const container = document.getElementById("container");
+ const animationCount = () => container.getAnimations({ subtree: true }).length;
+
+ assert_equals(animationCount(), 1, `An animation is running on the child initially with "display: block" on the container.`);
+
+ container.style.display = "none";
+ assert_equals(animationCount(), 0, `Setting "display: none" on the container canceled the animation.`);
+
+ container.style.marginLeft = "50px";
+ container.firstElementChild.style.marginLeft = "100px";
+ assert_equals(animationCount(), 0, `Manipulating styles on the container and a child element does not restart the animation.`);
+}, 'Elements in a "display: none" tree cannot start CSS Animations.');
+</script>
diff --git a/tests/wpt/tests/css/css-break/box-decoration-break-clone-005.tentative.html b/tests/wpt/tests/css/css-break/box-decoration-break-clone-005.tentative.html
new file mode 100644
index 00000000000..88ca6360f10
--- /dev/null
+++ b/tests/wpt/tests/css/css-break/box-decoration-break-clone-005.tentative.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/10553">
+<meta name="assert" content="Test how an abspos contained by a block with cloned box decoration behaves.">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="columns:2; column-fill:auto; gap:0; width:100px; height:100px; background:red;">
+ <!-- The expectation here (which might not be correct), is that the abspos
+ will start at the block-start padding edge of #container, and overlap
+ with cloned borders, although it fragments. Should it rather steer clear
+ of the cloned borders? -->
+ <div id="container" style="position:relative; box-decoration-break:clone; border:solid green; border-width:20px 0; border-bottom-color:red; padding:10px; height:80px;">
+ <div style="position:absolute; top:0; left:0; width:100%; height:180px; background:green;"></div>
+ </div>
+</div>
diff --git a/tests/wpt/tests/css/css-break/box-decoration-break-clone-006.html b/tests/wpt/tests/css/css-break/box-decoration-break-clone-006.html
new file mode 100644
index 00000000000..657f9f8442d
--- /dev/null
+++ b/tests/wpt/tests/css/css-break/box-decoration-break-clone-006.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-break/#break-decoration">
+<meta name="assert" content="Test how a float inside a block with cloned box decoration behaves.">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="columns:4; column-fill:auto; gap:0; width:100px; height:100px; background:red;">
+ <!-- #container takes up exactly two columns (2 * 40px of box decorations in
+ each column, plus 40px of content box height). The float will steer clear
+ of its cloned border at the bottom of the first column and at the start
+ of the second column, but the border at the end of the second column is
+ just a regular end border, not a cloned one, so the float will overlap
+ with it. -->
+ <div id="container" style="box-decoration-break:clone; border:solid green; border-width:40px 0; height:40px;">
+ <div style="float:left; width:100%; height:280px; background:green;"></div>
+ </div>
+</div>
diff --git a/tests/wpt/tests/css/css-break/box-decoration-break-clone-007.html b/tests/wpt/tests/css/css-break/box-decoration-break-clone-007.html
new file mode 100644
index 00000000000..f2855edaff8
--- /dev/null
+++ b/tests/wpt/tests/css/css-break/box-decoration-break-clone-007.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-break/#break-decoration">
+<meta name="assert" content="box-sizing:border-box and cloned box decorations.">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="columns:2; column-fill:auto; gap:0; width:100px; height:100px; background:red;">
+ <div style="box-decoration-break:clone; border:10px solid green; box-sizing:border-box; height:200px; background:green;"></div>
+</div>
diff --git a/tests/wpt/tests/css/css-break/box-decoration-break-clone-008.html b/tests/wpt/tests/css/css-break/box-decoration-break-clone-008.html
new file mode 100644
index 00000000000..5f132318835
--- /dev/null
+++ b/tests/wpt/tests/css/css-break/box-decoration-break-clone-008.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-break/#break-decoration">
+<meta name="assert" content="Cloned box decorations, including margins.">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="columns:2; column-fill:auto; gap:0; width:100px; height:100px; background:red;">
+ <div style="display:flow-root; background:green;">
+ <div style="box-decoration-break:clone; margin:10px 0; padding:10px 0;">
+ <div style="height:100px;"></div>
+ </div>
+ <div style="height:40px;"></div>
+ </div>
+</div>
diff --git a/tests/wpt/tests/css/css-break/box-decoration-break-clone-009-ref.html b/tests/wpt/tests/css/css-break/box-decoration-break-clone-009-ref.html
new file mode 100644
index 00000000000..23be78f87fc
--- /dev/null
+++ b/tests/wpt/tests/css/css-break/box-decoration-break-clone-009-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<p>There should be two squares below, with a hotpink outline and a cyan "shadow".</p>
+<div style="margin:20px; display:flex; width:230px; height:100px; justify-content:space-between;">
+ <div style="width:100px; height:100px; box-shadow:0 0 0 10px cyan; outline: 5px solid hotpink;"></div>
+ <div style="width:100px; height:100px; box-shadow:0 0 0 10px cyan; outline: 5px solid hotpink;"></div>
+</div>
diff --git a/tests/wpt/tests/css/css-break/box-decoration-break-clone-009.html b/tests/wpt/tests/css/css-break/box-decoration-break-clone-009.html
new file mode 100644
index 00000000000..1db904332ae
--- /dev/null
+++ b/tests/wpt/tests/css/css-break/box-decoration-break-clone-009.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-break/#break-decoration">
+<meta name="assert" content="Cloned box decorations, with outline and shadow.">
+<link rel="match" href="box-decoration-break-clone-009-ref.html">
+<p>There should be two squares below, with a hotpink outline and a cyan "shadow".</p>
+<div style="margin:20px; columns:2; gap:30px; column-fill:auto; width:230px; height:100px;">
+ <div style="box-decoration-break:clone; height:200px; box-shadow:0 0 0 10px cyan; outline: 5px solid hotpink;">
+ </div>
+</div>
diff --git a/tests/wpt/tests/css/css-color/color-mix-non-srgb-001-ref.html b/tests/wpt/tests/css/css-color/color-mix-non-srgb-001-ref.html
index 15556737c7f..e143350866d 100644
--- a/tests/wpt/tests/css/css-color/color-mix-non-srgb-001-ref.html
+++ b/tests/wpt/tests/css/css-color/color-mix-non-srgb-001-ref.html
@@ -1,6 +1,6 @@
<!DOCTYPE html>
<meta charset="utf-8">
-<link rel="author" href="mailto:barret@brennie.ca" title="Barret Rennie">
+<link rel="author" href="mailto:beth@brennie.ca" title="Beth Rennie">
<link rel="author" href="https://mozilla.org" title="Mozilla">
<style>
div { color: black; }
diff --git a/tests/wpt/tests/css/css-color/color-mix-non-srgb-001.html b/tests/wpt/tests/css/css-color/color-mix-non-srgb-001.html
index 7563581a4cf..550a919d241 100644
--- a/tests/wpt/tests/css/css-color/color-mix-non-srgb-001.html
+++ b/tests/wpt/tests/css/css-color/color-mix-non-srgb-001.html
@@ -2,7 +2,7 @@
<meta charset="utf-8">
<link rel="help" href="https://drafts.csswg.org/css-color-5/#color-mix">
<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1703356">
-<link rel="author" href="mailto:barret@brennie.ca" title="Barret Rennie">
+<link rel="author" href="mailto:beth@brennie.ca" title="Beth Rennie">
<link rel="author" href="https://mozilla.org" title="Mozilla">
<link rel="match" href="./color-mix-non-srgb-001-ref.html">
<style>
diff --git a/tests/wpt/tests/css/css-contain/content-visibility/content-visibility-auto-print-ref.html b/tests/wpt/tests/css/css-contain/content-visibility/content-visibility-auto-print-ref.html
new file mode 100644
index 00000000000..6f500daa743
--- /dev/null
+++ b/tests/wpt/tests/css/css-contain/content-visibility/content-visibility-auto-print-ref.html
@@ -0,0 +1,4 @@
+<!doctype html>
+<meta charset=utf-8>
+<div style="height: 500px"></div>
+<div>Hello World</div>
diff --git a/tests/wpt/tests/css/css-contain/content-visibility/content-visibility-auto-print.html b/tests/wpt/tests/css/css-contain/content-visibility/content-visibility-auto-print.html
new file mode 100644
index 00000000000..35bc333e879
--- /dev/null
+++ b/tests/wpt/tests/css/css-contain/content-visibility/content-visibility-auto-print.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<meta charset=utf-8>
+<link rel=match href="content-visibility-auto-print-ref.html">
+<link rel=help href="https://drafts.csswg.org/css-contain/#content-visibility">
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1907081">
+<!-- Pushes the following link onto page 3 of the print output: -->
+<div style="height: 500px"></div>
+<div style="content-visibility: auto">Hello World</div>
diff --git a/tests/wpt/tests/css/css-contain/content-visibility/content-visibility-auto-svg-image-ref.html b/tests/wpt/tests/css/css-contain/content-visibility/content-visibility-auto-svg-image-ref.html
new file mode 100644
index 00000000000..39939a6b33b
--- /dev/null
+++ b/tests/wpt/tests/css/css-contain/content-visibility/content-visibility-auto-svg-image-ref.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<meta charset=utf-8>
+<body>
+ <svg xmlns="http://www.w3.org/2000/svg" width="400" height="100">
+ <foreignObject width="100%" height="100%">
+ <div xmlns="http://www.w3.org/1999/xhtml" style="content-visibility: auto;">
+ <p style="font: 50px sans-serif;">Hello World</p>
+ </div>
+ </foreignObject>
+ </svg>
+</body>
diff --git a/tests/wpt/tests/css/css-contain/content-visibility/content-visibility-auto-svg-image.html b/tests/wpt/tests/css/css-contain/content-visibility/content-visibility-auto-svg-image.html
new file mode 100644
index 00000000000..46de764072d
--- /dev/null
+++ b/tests/wpt/tests/css/css-contain/content-visibility/content-visibility-auto-svg-image.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset=utf-8>
+<link rel=match href="content-visibility-auto-svg-image-ref.html">
+<link rel=help href="https://drafts.csswg.org/css-contain/#content-visibility">
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1894546">
+<body>
+
+<script>
+const svg = `
+ <svg xmlns="http://www.w3.org/2000/svg" width="400" height="100">
+ <foreignObject width="100%" height="100%">
+ <div xmlns="http://www.w3.org/1999/xhtml" style="content-visibility: auto;">
+ <p style="font: 50px sans-serif;">Hello World</p>
+ </div>
+ </foreignObject>
+ </svg>`;
+
+const data = `data:image/svg+xml,${encodeURIComponent(svg)}`;
+const svgImage = new Image();
+svgImage.src = data;
+
+document.body.appendChild(svgImage);
+</script>
+
+</body>
diff --git a/tests/wpt/tests/css/css-masking/clip-path/animations/clip-path-animation-cancel-ref.html b/tests/wpt/tests/css/css-masking/clip-path/animations/clip-path-animation-cancel-ref.html
new file mode 100644
index 00000000000..661e7b3be84
--- /dev/null
+++ b/tests/wpt/tests/css/css-masking/clip-path/animations/clip-path-animation-cancel-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<style>
+ .container {
+ width: 100px;
+ height: 100px;
+ background-color: green;
+ clip-path: circle(20% at 20% 20%);
+ }
+</style>
+
+<body>
+ <div class="container"></div>
+</body>
+
+</html>
diff --git a/tests/wpt/tests/css/css-masking/clip-path/animations/clip-path-animation-cancel.html b/tests/wpt/tests/css/css-masking/clip-path/animations/clip-path-animation-cancel.html
new file mode 100644
index 00000000000..f92a016662b
--- /dev/null
+++ b/tests/wpt/tests/css/css-masking/clip-path/animations/clip-path-animation-cancel.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<link rel="help" href="https://drafts.csswg.org/css-shapes-1/#basic-shape-interpolation">
+<link rel="match" href="clip-path-animation-cancel-ref.html">
+<style>
+ /* This test ensures that canceling an animation properly results in a
+ repaint. If this does not happen, the animation will remain stuck until
+ invalidated for another reason. */
+ .container {
+ width: 100px;
+ height: 100px;
+ background-color: green;
+ animation: clippath 10s;
+ clip-path: circle(20% at 20% 20%);
+ }
+
+ @keyframes clippath {
+ 0% {
+ clip-path: circle(35% at 35% 35%);
+ }
+
+ 100% {
+ clip-path: circle(50% at 50% 50%);
+ }
+ }
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/web-animations/resources/timing-utils.js"></script>
+
+<body>
+ <div class="container"></div>
+
+ <script>
+ document.getAnimations()[0].ready.then(() => {
+ document.getAnimations()[0].cancel();
+ requestAnimationFrame(takeScreenshot);
+ });
+ </script>
+</body>
+
+</html>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-022.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-022.tentative.html
new file mode 100644
index 00000000000..1ae5d94465a
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-022.tentative.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: when clamping by lines, margins are respected</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/line-clamp-022-ref.html">
+<meta name="assert" content="When line-clamp is used with a number of lines, the box and its clamped children should be sized taking margins into account.">
+<style>
+.clamp {
+ line-clamp: 4;
+ font: 16px / 32px serif;
+ padding: 4px;
+ background-color: yellow;
+ border: 2px solid black;
+}
+.inner {
+ background-color: purple;
+ margin: 4px;
+ /* Having a border means the margins of the .inner boxes won't collapse */
+ border: 1px solid black;
+}
+.inner .inner {
+ background-color: orange;
+ white-space: pre;
+}
+</style>
+<div class="clamp"><div class="inner"><div class="inner">Line 1
+Line 2
+Line 3
+Line 4
+Line 5
+Line 6</div></div></div>
+<p>Following content.</p>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-023.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-023.tentative.html
new file mode 100644
index 00000000000..0e11ba4d862
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-023.tentative.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: when clamping by lines, collapsed margins are respected</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/line-clamp-023-ref.html">
+<meta name="assert" content="When line-clamp is used with a number of lines, the box and its clamped children should be sized taking margin collapsing into account.">
+<style>
+.clamp {
+ line-clamp: 4;
+ font: 16px / 32px serif;
+ padding: 4px;
+ background-color: yellow;
+ border: 2px solid black;
+}
+.inner {
+ background-color: orange;
+ margin: 4px;
+ /* There is no border, so the margins of the .inner boxes will collapse */
+}
+.inner .inner {
+ white-space: pre;
+}
+</style>
+<div class="clamp"><div class="inner"><div class="inner">Line 1
+Line 2
+Line 3
+Line 4
+Line 5
+Line 6</div></div></div>
+<p>Following content.</p>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-024.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-024.tentative.html
new file mode 100644
index 00000000000..0efb474a11f
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-024.tentative.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: when clamping by lines, margins of hidden boxes are ignored</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/line-clamp-022-ref.html">
+<meta name="assert" content="When line-clamp is used with a number of lines, the margins of boxes after the clamp point should not affect the sizing of boxes.">
+<style>
+.clamp {
+ line-clamp: 4;
+ font: 16px / 32px serif;
+ padding: 4px;
+ background-color: yellow;
+ border: 2px solid black;
+}
+.inner {
+ background-color: purple;
+ margin: 4px;
+ /* Having a border means the margins of the .inner boxes won't collapse */
+ border: 1px solid black;
+}
+.inner .inner {
+ background-color: orange;
+ white-space: pre;
+}
+.clamped-margin {
+ /* These boxes are after the clamp point, so their margins should not affect
+ * the size of their parent boxes. */
+ background-color: red;
+ margin: 20px;
+ height: 20px;
+}
+</style>
+<div class="clamp">
+<div class="inner">
+<div class="inner">Line 1
+Line 2
+Line 3
+Line 4
+Line 5
+Line 6</div>
+<div class="clamped-margin"></div>
+</div>
+<div class="clamped-margin"></div>
+</div>
+<p>Following content.</p>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-025.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-025.tentative.html
new file mode 100644
index 00000000000..b2a2c54f450
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-025.tentative.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: when clamping by lines, margins of hidden boxes are ignored</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/line-clamp-023-ref.html">
+<meta name="assert" content="When line-clamp is used with a number of lines, the box and its clamped children should be sized taking margins into account.">
+<style>
+.clamp {
+ line-clamp: 4;
+ font: 16px / 32px serif;
+ padding: 4px;
+ background-color: yellow;
+ border: 2px solid black;
+}
+.inner {
+ background-color: orange;
+ margin: 4px;
+ /* There is no border, so the margins of the .inner boxes will collapse */
+}
+.inner .inner {
+ white-space: pre;
+}
+.clamped-margin {
+ /* These boxes are after the clamp point, so their margins should not affect
+ * the size of their parent boxes. */
+ background-color: red;
+ margin: 20px;
+ height: 20px;
+}
+</style>
+<div class="clamp">
+<div class="inner">
+<div class="inner">Line 1
+Line 2
+Line 3
+Line 4
+Line 5
+Line 6</div>
+<div class="clamped-margin"></div>
+</div>
+<div class="clamped-margin"></div>
+</div>
+<p>Following content.</p>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-026.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-026.tentative.html
new file mode 100644
index 00000000000..329d12d10bd
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-026.tentative.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: ruby annotations after the clamp point are not rendered</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/line-clamp-026-ref.html">
+<meta name="assert" content="Ruby annotations are part of a line box's content, and therefore must not be rendered if the line box wouldn't be">
+<style>
+.clamp {
+ line-clamp: 3;
+
+ font: 16px / 32px serif;
+ white-space: pre-wrap;
+}
+.ruby-under ruby {
+ ruby-position: under;
+}
+</style>
+<div class="clamp"><ruby>一<rt>いち</ruby><ruby>行<rt>ぎょう</ruby><ruby>目<rt>め</ruby>
+<span class="ruby-under"><ruby>ニ<rt>に</ruby><ruby>行<rt>ぎょう</ruby><ruby>目<rt>め</ruby></span>
+<ruby>三<rt><ruby>さん<rt>san</ruby></ruby><ruby>行<rt><ruby>ぎょう<rt>gyou</ruby></ruby><ruby>目<rt><ruby>め<rt>me</ruby></ruby>
+<ruby>四<rt>よん</ruby><ruby>行<rt>ぎょう</ruby><ruby>目<rt>め</ruby>
+<span class="ruby-under"><ruby>五<rt>ご</ruby><ruby>行<rt>ぎょう</ruby><ruby>目<rt>め</ruby></span>
+<ruby>六<rt><ruby>ろく<rt>roku</ruby></ruby><ruby>行<rt><ruby>ぎょう<rt>gyou</ruby></ruby><ruby>目<rt><ruby>め<rt>me</ruby></ruby></div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-027.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-027.tentative.html
new file mode 100644
index 00000000000..b2a376b2bd9
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-027.tentative.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: sizing of line-clamp elements with ruby annotations</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/line-clamp-027-ref.html">
+<meta name="assert" content="The sizing of a line-clamp element should be exactly as if it had no content after clamp, even if there are ruby annotations involved.">
+<style>
+.clamp {
+ line-clamp: 3;
+ font-size: 16px / 16px serif;
+ white-space: pre-wrap;
+ background-color: yellow;
+}
+ruby.under {
+ ruby-position: under;
+}
+.large {
+ font-size: 3em;
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+<ruby class="under">Line 3<rt>ruby</ruby>
+<ruby>Line 4<rt class="large">ruby</ruby>
+Line 5
+<ruby class="under">Line 6<rt class="large">ruby</ruby></div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-017.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-017.tentative.html
new file mode 100644
index 00000000000..376c69a2106
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-017.tentative.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: `line-clamp: auto` with the clamp point in a descendant</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-005-ref.html">
+<meta name="assert" content="line-clamp: auto should work when the clamp point is in a descendant block box">
+<style>
+.clamp {
+ line-clamp: auto;
+ max-height: 4lh;
+ font: 16px / 32px serif;
+ white-space: pre;
+ background-color: yellow;
+ padding: 0 4px;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2
+Line 3
+Line 4
+Line 5
+Line 6</div></div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-018.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-018.tentative.html
new file mode 100644
index 00000000000..deb1f42431d
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-018.tentative.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: `line-clamp: auto` with the clamp point in a descendant with bp</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/line-clamp-auto-018-ref.html">
+<meta name="assert" content="If line-clamp: auto has the clamp point in a descendant, it should take its border and padding into account">
+<style>
+.clamp {
+ line-clamp: auto;
+ max-height: 5lh;
+ font: 16px / 32px serif;
+ white-space: pre;
+ background-color: yellow;
+}
+.clamp > div {
+ padding: 2px;
+ border: 2px solid black;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2
+Line 3
+Line 4
+Line 5
+Line 6</div></div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-019.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-019.tentative.html
new file mode 100644
index 00000000000..82c53dc6bbe
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-019.tentative.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: `line-clamp: auto` with the clamp point in a descendant with bp</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/line-clamp-auto-019-ref.html">
+<meta name="assert" content="If line-clamp: auto has the clamp point in a descendant, it should take its border and padding into account">
+<style>
+.clamp {
+ line-clamp: auto;
+ max-height: 5lh;
+ font: 16px / 32px serif;
+ white-space: pre;
+ background-color: yellow;
+}
+.clamp > div {
+ /* The total block-size bp across the top and bottom is 2*(14+2) = 32px, same as the line-height */
+ padding: 14px;
+ border: 2px solid black;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2
+Line 3
+Line 4
+Line 5
+Line 6</div></div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-020.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-020.tentative.html
new file mode 100644
index 00000000000..eb84151ca66
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-020.tentative.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: `line-clamp: auto` with the clamp point in a descendant with bmp</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/line-clamp-auto-020-ref.html">
+<meta name="assert" content="If line-clamp: auto has the clamp point in a descendant, it should take its bmp into account">
+<style>
+.clamp {
+ line-clamp: auto;
+ max-height: 6lh;
+ font: 16px / 32px serif;
+ white-space: pre;
+ background-color: yellow;
+}
+.clamp > div {
+ /* The total block-size bpm across the top and bottom is 2*(15+2) = 34px, greater than the line-height */
+ padding: 15px;
+ border: 2px solid black;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2
+Line 3
+Line 4
+Line 5
+Line 6</div></div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-021.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-021.tentative.html
new file mode 100644
index 00000000000..90c33755bee
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-021.tentative.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: `line-clamp: auto` with multiple descendants</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/line-clamp-auto-021-ref.html">
+<meta name="assert" content="line-clamp: auto works well with multiple descendants">
+<style>
+.clamp {
+ line-clamp: auto;
+ max-height: 5lh;
+ font: 16px / 32px serif;
+ background-color: yellow;
+}
+.clamp > div {
+ /*
+ * The total border across the top and bottom of one element is 16px.
+ * The top and bottom borders across two elements add up to 32px = 1lh.
+ */
+ padding: 6px;
+ border: 2px solid black;
+ white-space: pre;
+}
+</style>
+<div class="clamp">
+<div>Line 1
+Line 2
+Line 3</div>
+<div>Line 4
+Line 5
+Line 6</div>
+<div>Line 7
+Line 8
+Line 9</div>
+</div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-022.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-022.tentative.html
new file mode 100644
index 00000000000..7834c9c3ae0
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-022.tentative.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: `line-clamp: auto` with multiple descendants</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/line-clamp-auto-021-ref.html">
+<meta name="assert" content="line-clamp: auto works well with multiple descendants">
+<style>
+.clamp {
+ line-clamp: auto;
+ max-height: 5.5lh;
+ font: 16px / 32px serif;
+ background-color: yellow;
+}
+.clamp > div {
+ /*
+ * The total border across the top and bottom of one element is 16px.
+ * The top and bottom borders across two elements add up to 32px = 1lh.
+ */
+ padding: 6px;
+ border: 2px solid black;
+ white-space: pre;
+}
+</style>
+<div class="clamp">
+<div>Line 1
+Line 2
+Line 3</div>
+<div>Line 4
+Line 5
+Line 6</div>
+<div>Line 7
+Line 8
+Line 9</div>
+</div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-023.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-023.tentative.html
new file mode 100644
index 00000000000..d41d68d84a3
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-023.tentative.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: `line-clamp: auto` with multiple descendants</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/line-clamp-auto-023-ref.html">
+<meta name="assert" content="line-clamp: auto works well with multiple descendants">
+<style>
+.clamp {
+ line-clamp: auto;
+ max-height: 7lh;
+ font: 16px / 32px serif;
+ background-color: yellow;
+}
+.clamp > div {
+ /*
+ * The total border across the top and bottom of one element is 16px.
+ * The top and bottom borders across two elements add up to 32px = 1lh.
+ */
+ padding: 6px;
+ border: 2px solid black;
+ white-space: pre;
+}
+</style>
+<div class="clamp">
+<div>Line 1
+Line 2
+Line 3</div>
+<div>Line 4
+Line 5
+Line 6</div>
+<div>Line 7
+Line 8
+Line 9</div>
+</div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-024.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-024.tentative.html
new file mode 100644
index 00000000000..134dade711a
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-024.tentative.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: `line-clamp: auto` with multiple descendants</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/line-clamp-auto-023-ref.html">
+<meta name="assert" content="line-clamp: auto works well with multiple descendants">
+<style>
+.clamp {
+ line-clamp: auto;
+ max-height: 7.5lh;
+ font: 16px / 32px serif;
+ background-color: yellow;
+}
+.clamp > div {
+ /*
+ * The total border across the top and bottom of one element is 16px.
+ * The top and bottom borders across two elements add up to 32px = 1lh.
+ */
+ padding: 6px;
+ border: 2px solid black;
+ white-space: pre;
+}
+</style>
+<div class="clamp">
+<div>Line 1
+Line 2
+Line 3</div>
+<div>Line 4
+Line 5
+Line 6</div>
+<div>Line 7
+Line 8
+Line 9</div>
+</div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-025.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-025.tentative.html
new file mode 100644
index 00000000000..ee394b2a76d
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-025.tentative.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: abspos exactly at the clamp point is visible</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/line-clamp-auto-025-ref.html">
+<meta name="assert" content="The chosen clamp point with line-clamp: auto is the last one where the box size doesn't overflow. Since non-inline abspos have a clamp point after them, and they don't take up any space in the container, an abspos right at the boundary should be visible.">
+<style>
+.clamp {
+ line-clamp: auto;
+ max-height: 4lh;
+ font: 16px / 32px serif;
+ background-color: yellow;
+}
+.inner {
+ white-space: pre-wrap;
+}
+.abspos {
+ position: absolute;
+ width: 100px;
+ height: 100px;
+ background-color: skyblue;
+}
+</style>
+
+<div class="clamp">
+<div class="inner">Line 1
+Line 2
+Line 3
+Line 4
+</div>
+<div class="abspos"></div>
+<div class="inner">Line 5
+Line 6</div>
+</div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-026.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-026.tentative.html
new file mode 100644
index 00000000000..f616cbed028
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-026.tentative.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: abspos exactly at the clamp point is visible</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/line-clamp-auto-026-ref.html">
+<meta name="assert" content="The chosen clamp point with line-clamp: auto is the last one where the box size doesn't overflow. Since non-inline abspos have a clamp point after them, and they don't take up any space in the container, an abspos right at the boundary should be visible. If there's bottom padding in a container box, that counts as effectively shrinking the boundary.">
+<style>
+.clamp {
+ line-clamp: auto;
+ max-height: calc(4lh + 2 * 2px);
+ font: 16px / 32px serif;
+ background-color: yellow;
+}
+.padding {
+ padding: 2px;
+}
+.inner {
+ white-space: pre-wrap;
+}
+.abspos {
+ position: absolute;
+ width: 100px;
+ height: 100px;
+ background-color: skyblue;
+}
+</style>
+
+<div class="clamp">
+<div class="padding">
+<div class="inner">Line 1
+Line 2
+Line 3
+Line 4
+</div>
+<div class="abspos"></div>
+<div class="inner">Line 5
+Line 6</div>
+</div>
+</div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-027.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-027.tentative.html
new file mode 100644
index 00000000000..9ed461d8cfb
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-027.tentative.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: `line-clamp: auto` accounts for margin</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/line-clamp-auto-027-ref.html">
+<meta name="assert" content="With `line-clamp: auto`, if a clamp point might fall inside a box, its margins are accounted for.">
+<style>
+.clamp {
+ line-clamp: auto;
+ max-height: 6lh;
+ font: 16px / 32px serif;
+ white-space: pre;
+ background-color: yellow;
+}
+.clamp > div {
+ margin: 17px;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2
+Line 3
+Line 4
+Line 5
+Line 6</div></div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-028.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-028.tentative.html
new file mode 100644
index 00000000000..e3bcf375bc5
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-028.tentative.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: `line-clamp: auto` accounts for margin</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/line-clamp-auto-028-ref.html">
+<meta name="assert" content="With `line-clamp: auto`, if a clamp point might fall inside a box, its margins are accounted for.">
+<style>
+.clamp {
+ line-clamp: auto;
+ max-height: 5lh;
+ font: 16px / 32px serif;
+ white-space: pre;
+ background-color: yellow;
+}
+.clamp > div {
+ margin: 16px;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2
+Line 3
+Line 4
+Line 5
+Line 6</div></div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-029.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-029.tentative.html
new file mode 100644
index 00000000000..17a3f1a78c1
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-029.tentative.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: `line-clamp: auto` accounts for collapsing margins</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/line-clamp-auto-029-ref.html">
+<meta name="assert" content="With `line-clamp: auto`, if a clamp point might fall inside a box, its margins are accounted for correctly, even if they collapse.">
+<style>
+.clamp {
+ line-clamp: auto;
+ max-height: 5lh;
+ font: 16px / 32px serif;
+ white-space: pre;
+ background-color: yellow;
+ margin: 4px;
+}
+.clamp div {
+ margin: 4px;
+}
+</style>
+<div class="clamp"><div><div><div>Line 1
+Line 2
+Line 3
+Line 4
+Line 5
+Line 6</div></div></div></div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-030.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-030.tentative.html
new file mode 100644
index 00000000000..2a3a73b3e71
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-030.tentative.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: `line-clamp: auto` accounts for collapsing margins</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/line-clamp-auto-029-ref.html">
+<meta name="assert" content="With `line-clamp: auto`, if a clamp point might fall inside a box, its margins are accounted for correctly, even if they collapse.">
+<style>
+.clamp {
+ line-clamp: auto;
+ max-height: calc(4lh + 2 * 4px);
+ font: 16px / 32px serif;
+ white-space: pre;
+ background-color: yellow;
+ margin: 4px;
+}
+.clamp div {
+ margin: 4px;
+}
+</style>
+<div class="clamp"><div><div><div>Line 1
+Line 2
+Line 3
+Line 4
+Line 5
+Line 6</div></div></div></div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-031.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-031.tentative.html
new file mode 100644
index 00000000000..a723430c83e
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-031.tentative.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: `line-clamp: auto` margin collapsing accounts for self-collapsing boxes</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/line-clamp-auto-031-ref.html">
+<meta name="assert" content="With `line-clamp: auto`, if a clamp point might fall inside a box, its margins are accounted for correctly. This is the case even for self-collapsing boxes before the clamp point.">
+<style>
+.clamp {
+ line-clamp: auto;
+ max-height: calc(4lh + 2 * 5px);
+ font: 16px / 32px serif;
+ background-color: orange;
+}
+.inner {
+ white-space: pre;
+ margin: 5px;
+ background-color: yellow;
+}
+.collapse-through {
+ margin: 10px;
+}
+.abspos {
+ position: absolute;
+ right: 0;
+ height: 100px;
+ width: 100px;
+ background-color: skyblue;
+}
+</style>
+
+<!--
+ The bottom margin of the first `.inner` ends at the clamp boundary, and the
+ bottom margin of `.collapse-through` ends after it. The clamp point therefore
+ is before `.collapse-through`, and so `.abpos` won't be visible.
+-->
+
+<div class="clamp">
+<div class="inner">Line 1
+Line 2
+Line 3
+Line 4</div>
+<div class="collapse-through"></div>
+<div class="abspos"></div>
+<div class="inner">Line 5
+Line 6
+Line 7
+Line 8</div>
+</div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-032.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-032.tentative.html
new file mode 100644
index 00000000000..17c6981ae33
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-auto-032.tentative.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: `line-clamp: auto` margin collapsing accounts for self-collapsing boxes</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/line-clamp-auto-032-ref.html">
+<meta name="assert" content="With `line-clamp: auto`, if a clamp point might fall inside a box, its margins are accounted for correctly. This is the case even for self-collapsing boxes before the clamp point.">
+<style>
+.clamp {
+ line-clamp: auto;
+ max-height: calc(4lh + 2 * 5px);
+ font: 16px / 32px serif;
+ background-color: orange;
+}
+.inner {
+ white-space: pre;
+ margin: 5px;
+ background-color: yellow;
+}
+.collapse-through {
+ margin: 5px;
+}
+.abspos {
+ position: absolute;
+ right: 0;
+ height: 100px;
+ width: 100px;
+ background-color: skyblue;
+}
+</style>
+
+<!--
+ The bottom margins of both the first `.inner` and of `.collapse-through` end
+ at the clamp boundary, since the bottom margin of `.inner` collapses through
+ `.collapse-through`. This also puts the static position of `.abspos` at the
+ clamp boundary, which means it will be visible.
+-->
+
+<div class="clamp">
+<div class="inner">Line 1
+Line 2
+Line 3
+Line 4</div>
+<div class="collapse-through"></div>
+<div class="abspos"></div>
+<div class="inner">Line 5
+Line 6
+Line 7
+Line 8</div>
+</div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-with-abspos-012.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-with-abspos-012.tentative.html
new file mode 100644
index 00000000000..bed065f3838
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-with-abspos-012.tentative.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: line-clamp: auto hidden block-level abspos</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-005-ref.html">
+<meta name="assert" content="Block-level abspos should still be hidden if it is after the clamp point with line-clamp: auto">
+<style>
+.clamp {
+ line-clamp: auto;
+ max-height: 4lh;
+ font: 16px / 32px serif;
+ padding: 0 4px;
+ background-color: yellow;
+}
+.inner {
+ white-space: pre;
+}
+.abspos {
+ position: absolute;
+ width: 100px;
+ height: 100px;
+ background-color: skyblue;
+}
+</style>
+
+<div class="clamp">
+<div class="inner">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div>
+<div class="inner">Line 6</div>
+<div class="abspos"></div>
+</div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-with-abspos-013.tentative.html b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-with-abspos-013.tentative.html
new file mode 100644
index 00000000000..ccea125f724
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/line-clamp-with-abspos-013.tentative.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: line-clamp: auto hidden block-level abspos</title>
+<link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-005-ref.html">
+<meta name="assert" content="Block-level abspos should still be hidden if it is after the clamp point with line-clamp: auto, even if it lies on the boundary because the automatic size of previous boxes was adjusted.">
+<style>
+.clamp {
+ line-clamp: auto;
+ max-height: 4lh;
+ font: 16px / 32px serif;
+ padding: 0 4px;
+ background-color: yellow;
+}
+.inner {
+ white-space: pre;
+}
+.abspos {
+ position: absolute;
+ width: 100px;
+ height: 100px;
+ background-color: skyblue;
+}
+</style>
+
+<div class="clamp">
+<div class="inner">Line 1
+Line 2
+Line 3
+Line 4
+Line 5
+Line 6
+</div>
+<div class="abspos"></div>
+</div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-022-ref.html b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-022-ref.html
new file mode 100644
index 00000000000..7df05662f07
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-022-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+ font: 16px / 32px serif;
+ padding: 4px;
+ background-color: yellow;
+ border: 2px solid black;
+}
+.inner {
+ background-color: purple;
+ margin: 4px;
+ border: 1px solid black;
+}
+.inner .inner {
+ background-color: orange;
+ white-space: pre;
+}
+</style>
+<div class="clamp"><div class="inner"><div class="inner">Line 1
+Line 2
+Line 3
+Line 4…</div></div></div>
+<p>Following content.</p>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-023-ref.html b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-023-ref.html
new file mode 100644
index 00000000000..55ca1ca92be
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-023-ref.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+ font: 16px / 32px serif;
+ padding: 4px;
+ background-color: yellow;
+ border: 2px solid black;
+}
+.inner {
+ background-color: orange;
+ margin: 4px;
+}
+.inner .inner {
+ white-space: pre;
+}
+</style>
+<div class="clamp"><div class="inner"><div class="inner">Line 1
+Line 2
+Line 3
+Line 4…</div></div></div>
+<p>Following content.</p>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-026-ref.html b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-026-ref.html
new file mode 100644
index 00000000000..b6e816f997c
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-026-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+ font: 16px / 32px serif;
+ white-space: pre-wrap;
+}
+.ruby-under ruby {
+ ruby-position: under;
+}
+</style>
+<div class="clamp"><ruby>一<rt>いち</ruby><ruby>行<rt>ぎょう</ruby><ruby>目<rt>め</ruby>
+<span class="ruby-under"><ruby>ニ<rt>に</ruby><ruby>行<rt>ぎょう</ruby><ruby>目<rt>め</ruby></span>
+<ruby>三<rt><ruby>さん<rt>san</ruby></ruby><ruby>行<rt><ruby>ぎょう<rt>gyou</ruby></ruby><ruby>目<rt><ruby>め<rt>me</ruby></ruby>…</div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-027-ref.html b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-027-ref.html
new file mode 100644
index 00000000000..01ed862e0a2
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-027-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+ font-size: 16px / 16px serif;
+ white-space: pre-wrap;
+ background-color: yellow;
+}
+ruby.under {
+ ruby-position: under;
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+<ruby class="under">Line 3<rt>ruby</ruby>…</div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-018-ref.html b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-018-ref.html
new file mode 100644
index 00000000000..4d24d766956
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-018-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+ display: flow-root;
+ font: 16px / 32px serif;
+ white-space: pre;
+ background-color: yellow;
+}
+.clamp > div {
+ padding: 2px;
+ border: 2px solid black;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2
+Line 3
+Line 4…</div></div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-019-ref.html b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-019-ref.html
new file mode 100644
index 00000000000..5dd739a1dab
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-019-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+ display: flow-root;
+ font: 16px / 32px serif;
+ white-space: pre;
+ background-color: yellow;
+}
+.clamp > div {
+ padding: 14px;
+ border: 2px solid black;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2
+Line 3
+Line 4…</div></div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-020-ref.html b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-020-ref.html
new file mode 100644
index 00000000000..ef111f1e4a8
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-020-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+ display: flow-root;
+ font: 16px / 32px serif;
+ white-space: pre;
+ background-color: yellow;
+}
+.clamp > div {
+ padding: 15px;
+ border: 2px solid black;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2
+Line 3
+Line 4…</div></div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-021-ref.html b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-021-ref.html
new file mode 100644
index 00000000000..9ecad287453
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-021-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+ display: flow-root;
+ font: 16px / 32px serif;
+ background-color: yellow;
+}
+.clamp > div {
+ padding: 6px;
+ border: 2px solid black;
+ white-space: pre;
+}
+</style>
+<div class="clamp">
+<div>Line 1
+Line 2
+Line 3</div>
+<div>Line 4…</div>
+</div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-023-ref.html b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-023-ref.html
new file mode 100644
index 00000000000..adb4b6dc6ed
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-023-ref.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+ display: flow-root;
+ font: 16px / 32px serif;
+ background-color: yellow;
+}
+.clamp > div {
+ padding: 6px;
+ border: 2px solid black;
+ white-space: pre;
+}
+</style>
+<div class="clamp">
+<div>Line 1
+Line 2
+Line 3</div>
+<div>Line 4
+Line 5
+Line 6…</div>
+</div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-025-ref.html b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-025-ref.html
new file mode 100644
index 00000000000..a76468d6d54
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-025-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+ font: 16px / 32px serif;
+ background-color: yellow;
+}
+.inner {
+ white-space: pre-wrap;
+}
+.abspos {
+ position: absolute;
+ width: 100px;
+ height: 100px;
+ background-color: skyblue;
+}
+</style>
+
+<div class="clamp">
+<div class="inner">Line 1
+Line 2
+Line 3
+Line 4…
+</div>
+<div class="abspos"></div>
+</div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-026-ref.html b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-026-ref.html
new file mode 100644
index 00000000000..44d503a0522
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-026-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+ font: 16px / 32px serif;
+ background-color: yellow;
+}
+.padding {
+ padding: 2px;
+}
+.inner {
+ white-space: pre-wrap;
+}
+.abspos {
+ position: absolute;
+ width: 100px;
+ height: 100px;
+ background-color: skyblue;
+}
+</style>
+
+<div class="clamp">
+<div class="padding">
+<div class="inner">Line 1
+Line 2
+Line 3
+Line 4…
+</div>
+<div class="abspos"></div>
+</div>
+</div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-027-ref.html b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-027-ref.html
new file mode 100644
index 00000000000..fa0ade7a61d
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-027-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+ display: flow-root;
+ font: 16px / 32px serif;
+ white-space: pre;
+ background-color: yellow;
+}
+.clamp > div {
+ margin: 17px;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2
+Line 3
+Line 4…</div></div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-028-ref.html b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-028-ref.html
new file mode 100644
index 00000000000..b70c6be4ffc
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-028-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+ display: flow-root;
+ font: 16px / 32px serif;
+ white-space: pre;
+ background-color: yellow;
+}
+.clamp > div {
+ margin: 16px;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2
+Line 3
+Line 4…</div></div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-029-ref.html b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-029-ref.html
new file mode 100644
index 00000000000..76cd5fb7d22
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-029-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+ display: flow-root;
+ font: 16px / 32px serif;
+ white-space: pre;
+ background-color: yellow;
+ margin: 4px;
+}
+.clamp div {
+ margin: 4px;
+}
+</style>
+<div class="clamp"><div><div><div>Line 1
+Line 2
+Line 3
+Line 4…</div></div></div></div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-031-ref.html b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-031-ref.html
new file mode 100644
index 00000000000..2d3c9d0261b
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-031-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+ display: flow-root;
+ font: 16px / 32px serif;
+ background-color: orange;
+}
+.inner {
+ white-space: pre;
+ margin: 5px;
+ background-color: yellow;
+}
+</style>
+
+<div class="clamp">
+<div class="inner">Line 1
+Line 2
+Line 3
+Line 4…</div>
+</div>
diff --git a/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-032-ref.html b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-032-ref.html
new file mode 100644
index 00000000000..3b32f988cd9
--- /dev/null
+++ b/tests/wpt/tests/css/css-overflow/line-clamp/reference/line-clamp-auto-032-ref.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+ display: flow-root;
+ font: 16px / 32px serif;
+ background-color: orange;
+}
+.inner {
+ white-space: pre;
+ margin: 5px;
+ background-color: yellow;
+}
+.collapse-through {
+ margin: 5px;
+}
+.abspos {
+ position: absolute;
+ right: 0;
+ height: 100px;
+ width: 100px;
+ background-color: skyblue;
+}
+</style>
+
+<div class="clamp">
+<div class="inner">Line 1
+Line 2
+Line 3
+Line 4…</div>
+<div class="collapse-through"></div>
+<div class="abspos"></div>
+</div>
diff --git a/tests/wpt/tests/css/css-page/page-background-001-print-ref.html b/tests/wpt/tests/css/css-page/page-background-001-print-ref.html
new file mode 100644
index 00000000000..8269582d041
--- /dev/null
+++ b/tests/wpt/tests/css/css-page/page-background-001-print-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<style>
+ body {
+ margin-left: 100px;
+ }
+</style>
+<img style="position:absolute; z-index:-1; top:0; left:0;" src="support/cat.png">
+There should be a cat in the top left corner on this page.
+<div style="break-before:page;">
+ But there should be no cat on this page.
+</div>
diff --git a/tests/wpt/tests/css/css-page/page-background-001-print.html b/tests/wpt/tests/css/css-page/page-background-001-print.html
new file mode 100644
index 00000000000..e1bc2196940
--- /dev/null
+++ b/tests/wpt/tests/css/css-page/page-background-001-print.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="http://crbug.com/351332645">
+<link rel="match" href="page-background-001-print-ref.html">
+<style>
+ body {
+ margin-left: 100px;
+ background: url(support/cat.png) no-repeat;
+ }
+</style>
+There should be a cat in the top left corner on this page.
+<div style="break-before:page;">
+ But there should be no cat on this page.
+</div>
diff --git a/tests/wpt/tests/css/css-page/page-background-002-print-ref.html b/tests/wpt/tests/css/css-page/page-background-002-print-ref.html
new file mode 100644
index 00000000000..997a28c65ac
--- /dev/null
+++ b/tests/wpt/tests/css/css-page/page-background-002-print-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<style>
+ @page {
+ size: 300px 50px;
+ margin: 0;
+ }
+ body {
+ margin-left: 100px;
+ }
+</style>
+<img style="position:absolute; z-index:-1; top:0; left:0;" src="support/cat.png">
+Cat head.
+<div style="break-before:page;">
+ Cat body.
+</div>
+<div style="break-before:page;">
+ No cat parts on this page.
+</div>
diff --git a/tests/wpt/tests/css/css-page/page-background-002-print.html b/tests/wpt/tests/css/css-page/page-background-002-print.html
new file mode 100644
index 00000000000..240d468ce44
--- /dev/null
+++ b/tests/wpt/tests/css/css-page/page-background-002-print.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="http://crbug.com/351332645">
+<link rel="match" href="page-background-002-print-ref.html">
+<style>
+ @page {
+ size: 300px 50px;
+ margin: 0;
+ }
+ body {
+ margin-left: 100px;
+ background: url(support/cat.png) no-repeat;
+ }
+</style>
+Cat head.
+<div style="break-before:page;">
+ Cat body.
+</div>
+<div style="break-before:page;">
+ No cat parts on this page.
+</div>
diff --git a/tests/wpt/tests/css/css-page/page-background-003-print-ref.html b/tests/wpt/tests/css/css-page/page-background-003-print-ref.html
new file mode 100644
index 00000000000..93f3c7e95ad
--- /dev/null
+++ b/tests/wpt/tests/css/css-page/page-background-003-print-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<style>
+ @page {
+ size: 500px 250px;
+ margin: 0;
+ }
+ body {
+ margin-left: 100px;
+ }
+</style>
+<div style="position:absolute; z-index:-1; top:0; left:0; overflow:clip; contain:size; width:100px; height:500px;">
+ <img src="support/cat.png" style="display:block;">
+ <img src="support/cat.png" style="display:block;">
+ <img src="support/cat.png" style="display:block;">
+ <img src="support/cat.png" style="display:block;">
+ <img src="support/cat.png" style="display:block;">
+ <img src="support/cat.png" style="display:block;">
+</div>
+There should be two and a half cats on this page.
+<div style="break-before:page;">
+ There should be two and a half cats on this page, too (and the ears of another
+ cat barely visible at the bottom). The previous page ends with a cat's head,
+ and this page starts with a cat's body.
+</div>
diff --git a/tests/wpt/tests/css/css-page/page-background-003-print.html b/tests/wpt/tests/css/css-page/page-background-003-print.html
new file mode 100644
index 00000000000..fbc72ecd7d7
--- /dev/null
+++ b/tests/wpt/tests/css/css-page/page-background-003-print.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="http://crbug.com/351332645">
+<link rel="match" href="page-background-003-print-ref.html">
+<style>
+ @page {
+ size: 500px 250px;
+ margin: 0;
+ }
+ body {
+ margin-left: 100px;
+ background: url(support/cat.png) repeat-y;
+ }
+</style>
+There should be two and a half cats on this page.
+<div style="break-before:page;">
+ There should be two and a half cats on this page, too (and the ears of another
+ cat barely visible at the bottom). The previous page ends with a cat's head,
+ and this page starts with a cat's body.
+</div>
diff --git a/tests/wpt/tests/css/css-properties-values-api/registered-property-computation-color-004.html b/tests/wpt/tests/css/css-properties-values-api/registered-property-computation-color-004.html
new file mode 100644
index 00000000000..66abd1b3c32
--- /dev/null
+++ b/tests/wpt/tests/css/css-properties-values-api/registered-property-computation-color-004.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-properties-values-api/#calculation-of-computed-values" />
+<link rel="match" href="/css/reference/ref-filled-green-100px-square-only.html">
+<style>
+ @property --a {
+ syntax: '<color>';
+ inherits: true;
+ initial-value: green;
+ }
+
+ body {
+ --a: 1em;
+ }
+
+ div {
+ width: 100px;
+ height: 100px;
+ background-color: var(--a);
+ }
+</style>
+<p>Test passes if there is a filled green square.</p>
+<div></div>
diff --git a/tests/wpt/tests/css/css-tables/collapsed-border-remove-row-group-ref.html b/tests/wpt/tests/css/css-tables/collapsed-border-remove-row-group-ref.html
new file mode 100644
index 00000000000..060f0651b69
--- /dev/null
+++ b/tests/wpt/tests/css/css-tables/collapsed-border-remove-row-group-ref.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS test reference</title>
+<style>
+ table {
+ border-collapse: collapse;
+ }
+
+ td {
+ border-bottom: 10px solid;
+ }
+</style>
+<table>
+ <thead>
+ <tr>
+ <td>Something</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Body</td>
+ </tr>
+ </tbody>
+</table>
diff --git a/tests/wpt/tests/css/css-tables/collapsed-border-remove-row-group.html b/tests/wpt/tests/css/css-tables/collapsed-border-remove-row-group.html
new file mode 100644
index 00000000000..39b89956c54
--- /dev/null
+++ b/tests/wpt/tests/css/css-tables/collapsed-border-remove-row-group.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>collapsed border recalculation as a result of removal</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<link rel="help" href="https://drafts.csswg.org/css-tables/#border-collapse-property">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1907289">
+<link rel="match" href="collapsed-border-remove-row-group-ref.html">
+<style>
+ table {
+ border-collapse: collapse;
+ }
+ td {
+ border-bottom: 10px solid;
+ }
+</style>
+<table>
+ <thead>
+ <tr>
+ <td>Something</td>
+ </tr>
+ </thead>
+ <thead id="removeMe">
+ <tr>
+ <td style="border: 0">Something</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Body</td>
+ </tr>
+ </tbody>
+</table>
+<script>
+ onload = function () {
+ removeMe.getBoundingClientRect();
+ removeMe.remove();
+ };
+</script>
diff --git a/tests/wpt/tests/css/css-transitions/pseudo-element-transform-ref.html b/tests/wpt/tests/css/css-transitions/pseudo-element-transform-ref.html
new file mode 100644
index 00000000000..05ea20b69c4
--- /dev/null
+++ b/tests/wpt/tests/css/css-transitions/pseudo-element-transform-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<title>Reference for transition on pseudo-element</title>
+<link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#before-after-content">
+<style>
+div {
+ width: 100px;
+ height: 100px;
+ background: rgb(255, 191, 0);
+}
+div::before {
+ content: "";
+ background: rgb(184, 115, 51);
+ width: 100px;
+ height: 100px;
+ transform: ScaleX(0.5);
+ display: block;
+ will-change: transform;
+}
+</style>
+<div></div>
diff --git a/tests/wpt/tests/css/css-transitions/pseudo-element-transform.html b/tests/wpt/tests/css/css-transitions/pseudo-element-transform.html
new file mode 100644
index 00000000000..1919835e3e0
--- /dev/null
+++ b/tests/wpt/tests/css/css-transitions/pseudo-element-transform.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Transition on pseudo-element</title>
+<link rel="help" href="http://www.w3.org/TR/CSS21/generate.html#before-after-content">
+<link rel="match" href="pseudo-element-transform-ref.html">
+<script src="/common/reftest-wait.js"></script>
+<style>
+div {
+ width: 100px;
+ height: 100px;
+ background: rgb(255, 191, 0);
+}
+div::before {
+ content: "";
+ background: rgb(184, 115, 51);
+ width: 100px;
+ height: 100px;
+ transition-property: transform;
+ transition-duration: 10000s;
+ /* This timing-function has zero-slope at progress = 0.5 preventing drift */
+ transition-timing-function: cubic-bezier(0, 1, 1, 0);
+ transform: ScaleX(0);
+ /* Make the pseudo element "transformable" as per
+ * https://www.w3.org/TR/css-transforms-1/#transformable-element.
+ */
+ display: block;
+}
+
+div.animated::before {
+ transform: scaleX(1);
+}
+</style>
+<div></div>
+<script>
+// This is a regression test for crbug.com/40475833
+// Ported to WPT in an effort to prune browser-specific tests.
+window.onload = async () => {
+ requestAnimationFrame(() => {
+ const target = document.querySelector('div');
+ target.classList.add('animated');
+ const anim = document.getAnimations()[0];
+ anim.ready.then(() => {
+ const duration = anim.effect.getTiming().duration;
+ anim.currentTime = duration / 2;
+ requestAnimationFrame(() => {
+ requestAnimationFrame(takeScreenshot);
+ });
+ });
+ });
+};
+</script>
+</html>
diff --git a/tests/wpt/tests/css/css-transitions/transitions-retarget.html b/tests/wpt/tests/css/css-transitions/transitions-retarget.html
new file mode 100644
index 00000000000..ee4c54a0b19
--- /dev/null
+++ b/tests/wpt/tests/css/css-transitions/transitions-retarget.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Retargeted CSS transition</title>
+<link rel="help" href="https://www.w3.org/TR/css-transitions-1/#starting">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="element">x</div>
+<style>
+ #element {
+ transition: transform 2000ms;
+ transition-timing-function: linear;
+ }
+</style>
+<script>
+promise_test(function(t) {
+ element.offsetTop; // Force recalc
+ element.style.transform = "rotateX(180deg)";
+ element.offsetTop; // Force recalc
+
+ assert_equals(element.getAnimations().length, 1, 'Transition creates an animation');
+ var animation = element.getAnimations()[0];
+
+ return animation.ready.then(function() {
+ assert_equals(element.getAnimations().length, 1, 'No new animations yet');
+ assert_equals(element.getAnimations()[0], animation);
+
+ element.style.transform = "rotateX(360deg)";
+ element.offsetTop; // Force recalc
+
+ assert_equals(element.getAnimations().length, 1, 'Retargeting transition results in only one animation');
+
+ var newAnimation = element.getAnimations()[0];
+ assert_not_equals(newAnimation, animation);
+ });
+}, 'Retargeting a transition should cause the old transition to be cancelled');
+</script>
diff --git a/tests/wpt/tests/css/css-values/calc-size/animation/interpolate-size-interpolation.html b/tests/wpt/tests/css/css-values/calc-size/animation/interpolate-size-interpolation.html
new file mode 100644
index 00000000000..ef2034ba480
--- /dev/null
+++ b/tests/wpt/tests/css/css-values/calc-size/animation/interpolate-size-interpolation.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>interpolate-size animations</title>
+<link rel="help" href="https://drafts.csswg.org/css-values-5/#calc-size">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+<body>
+<script>
+ test_not_animatable({
+ property: 'interpolate-size',
+ from: 'numeric-only',
+ to: 'allow-keywords',
+ underlying: 'numeric-only',
+ });
+ test_not_animatable({
+ property: 'interpolate-size',
+ from: 'allow-keywords',
+ to: 'numeric-only',
+ underlying: 'numeric-only',
+ });
+</script>
diff --git a/tests/wpt/tests/css/css-values/calc-size/interpolate-size-computed.html b/tests/wpt/tests/css/css-values/calc-size/interpolate-size-computed.html
new file mode 100644
index 00000000000..ca7fd706cb9
--- /dev/null
+++ b/tests/wpt/tests/css/css-values/calc-size/interpolate-size-computed.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>The interpolate-size property: computed values</title>
+<link rel="help" href="https://drafts.csswg.org/css-values-5/#interpolate-size">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/computed-testcommon.js"></script>
+<div id="target"></div>
+<script>
+test_computed_value('interpolate-size', 'numeric-only');
+test_computed_value('interpolate-size', 'allow-keywords');
+</script>
diff --git a/tests/wpt/tests/css/css-values/calc-size/interpolate-size-parsing.html b/tests/wpt/tests/css/css-values/calc-size/interpolate-size-parsing.html
new file mode 100644
index 00000000000..04c8cff8f36
--- /dev/null
+++ b/tests/wpt/tests/css/css-values/calc-size/interpolate-size-parsing.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>The interpolate-size property: parsing</title>
+<link rel="help" href="https://drafts.csswg.org/css-values-5/#interpolate-size">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+
+<script>
+test_valid_value('interpolate-size', 'numeric-only');
+test_valid_value('interpolate-size', 'allow-keywords');
+
+test_invalid_value('interpolate-size', 'auto');
+test_invalid_value('interpolate-size', 'none');
+test_invalid_value('interpolate-size', '100%');
+</script>
diff --git a/tests/wpt/tests/custom-elements/form-associated/label-delegatesFocus.html b/tests/wpt/tests/custom-elements/form-associated/label-delegatesFocus.html
index 74d31363c98..6f854faeb66 100644
--- a/tests/wpt/tests/custom-elements/form-associated/label-delegatesFocus.html
+++ b/tests/wpt/tests/custom-elements/form-associated/label-delegatesFocus.html
@@ -11,6 +11,11 @@
<my-custom-element id=custom></my-custom-element>
</form>
+<form>
+ <label for=custom2><span>label</span></label>
+ <my-custom-element id=custom2></my-custom-element>
+</form>
+
<script>
class MyCustomElement extends HTMLElement {
static formAssociated = true;
@@ -28,14 +33,25 @@ customElements.define('my-custom-element', MyCustomElement);
window.onload = () => {
promise_test(async () => {
const label = document.querySelector('label');
- const customElement = document.querySelector('my-custom-element');
+ const customElement = document.getElementById('custom');
const input = customElement.shadowRoot.querySelector('input');
- await new Promise((resolve) => {
- input.addEventListener("focus", resolve, {once: true});
- test_driver.click(label);
- });
+ let focused = false;
+ input.addEventListener("focus", evt => { focused = true; }, {once: true});
+ await test_driver.click(label);
+ assert_true(focused, "should have received focus");
assert_equals(document.activeElement, customElement);
assert_equals(customElement.shadowRoot.activeElement, input);
}, `Clicking on a label for a form associated custom element with delegatesFocus should focus the custom element's focus delegate.`);
+ promise_test(async () => {
+ const span = document.querySelector('span');
+ const customElement = document.getElementById('custom2');
+ const input = customElement.shadowRoot.querySelector('input');
+ let focused = false;
+ input.addEventListener("focus", evt => { focused = true; }, {once: true});
+ await test_driver.click(span);
+ assert_true(focused, "should have received focus");
+ assert_equals(document.activeElement, customElement);
+ assert_equals(customElement.shadowRoot.activeElement, input);
+ }, `Clicking on a span in a label for a form associated custom element with delegatesFocus should focus the custom element's focus delegate.`);
};
</script>
diff --git a/tests/wpt/tests/dom/nodes/moveBefore/tentative/input-moveBefore-crash.html b/tests/wpt/tests/dom/nodes/moveBefore/tentative/input-moveBefore-crash.html
new file mode 100644
index 00000000000..a41d06b024e
--- /dev/null
+++ b/tests/wpt/tests/dom/nodes/moveBefore/tentative/input-moveBefore-crash.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<body>
+<input id="input">
+<script>
+window.onload = () => {
+ document.body.moveBefore(document.querySelector("#input"), null);
+};
+</script>
diff --git a/tests/wpt/tests/dom/observable/tentative/observable-constructor.any.js b/tests/wpt/tests/dom/observable/tentative/observable-constructor.any.js
index 2cd2ee2b664..109ed284db0 100644
--- a/tests/wpt/tests/dom/observable/tentative/observable-constructor.any.js
+++ b/tests/wpt/tests/dom/observable/tentative/observable-constructor.any.js
@@ -688,10 +688,26 @@ test(() => {
assert_array_equals(results, ['subscribe() callback']);
ac.abort();
results.push('abort() returned');
+ // The reason the "inner" abort event handler is invoked first is because the
+ // "inner" AbortSignal is not a dependent signal (that would ordinarily get
+ // aborted after the parent, aka "outer" signal, is completely finished being
+ // aborted). Instead, the order of operations looks like this:
+ // 1. "Outer" signal begins to be aborted
+ // 2. Its abort algorithms [1] run [2]; the internal abort algorithm here is
+ // the "inner" Subscriber's "Close a subscription" [0].
+ // a. This signals abort on the "inner" Subscriber's signal, firing the
+ // abort event
+ // b. Then, the "inner" Subscriber's teardowns run.
+ // 3. Once the "outer" signal's abort algorithms are finished, the abort
+ // event is fired [3], triggering the outer abort handler.
+ //
+ // [0]: https://wicg.github.io/observable/#close-a-subscription
+ // [1]: https://dom.spec.whatwg.org/#abortsignal-abort-algorithms
+ // [2]: https://dom.spec.whatwg.org/#ref-for-abortsignal-abort-algorithms%E2%91%A2:~:text=For%20each%20algorithm%20of%20signal%E2%80%99s%20abort%20algorithms%3A%20run%20algorithm
+ // [3]: https://dom.spec.whatwg.org/#abortsignal-signal-abort:~:text=Fire%20an%20event%20named%20abort%20at%20signal
assert_array_equals(results, [
- 'subscribe() callback',
- 'outer abort handler', 'teardown 2', 'teardown 1',
- 'inner abort handler', 'abort() returned',
+ 'subscribe() callback', 'inner abort handler', 'teardown 2', 'teardown 1',
+ 'outer abort handler', 'abort() returned',
]);
assert_false(activeDuringTeardown1, 'should not be active during teardown callback 1');
assert_false(activeDuringTeardown2, 'should not be active during teardown callback 2');
@@ -699,6 +715,127 @@ test(() => {
assert_true(abortedDuringTeardown2, 'should be aborted during teardown callback 2');
}, "Unsubscription lifecycle");
+// In the usual consumer-initiated unsubscription case, when the AbortController
+// is aborted after subscription, teardowns run from upstream->downstream. This
+// is because for a given Subscriber, when a downstream signal is aborted
+// (`ac.signal` in this case), the "Close" algorithm prompts the Subscriber to
+// first abort *its* own signal (the one accessible via `Subscriber#signal`) and
+// then run its teardowns.
+//
+// This means upstream Subscribers get the first opportunity their teardowns
+// before the control flow is returned to downstream Subscribers to run *their*
+// teardowns (after they abort their internal signal).
+test(() => {
+ const results = [];
+ const upstream = new Observable(subscriber => {
+ subscriber.signal.addEventListener('abort',
+ e => results.push('upstream abort handler'), {once: true});
+ subscriber.addTeardown(
+ () => results.push(`upstream teardown. reason: ${subscriber.signal.reason}`));
+ });
+ const middle = new Observable(subscriber => {
+ subscriber.signal.addEventListener('abort',
+ e => results.push('middle abort handler'), {once: true});
+ subscriber.addTeardown(
+ () => results.push(`middle teardown. reason: ${subscriber.signal.reason}`));
+ upstream.subscribe({}, {signal: subscriber.signal});
+ });
+ const downstream = new Observable(subscriber => {
+ subscriber.signal.addEventListener('abort',
+ e => results.push('downstream abort handler'), {once: true});
+ subscriber.addTeardown(
+ () => results.push(`downstream teardown. reason: ${subscriber.signal.reason}`));
+ middle.subscribe({}, {signal: subscriber.signal});
+ });
+
+ const ac = new AbortController();
+ downstream.subscribe({}, {signal: ac.signal});
+ ac.abort('Abort!');
+ assert_array_equals(results, [
+ 'upstream abort handler',
+ 'upstream teardown. reason: Abort!',
+ 'middle abort handler',
+ 'middle teardown. reason: Abort!',
+ 'downstream abort handler',
+ 'downstream teardown. reason: Abort!',
+ ]);
+}, "Teardowns are called in upstream->downstream order on " +
+ "consumer-initiated unsubscription");
+
+// This test is like the above, but asserts the exact opposite order of
+// teardowns. This is because, since the Subscriber's signal is aborted
+// immediately upon construction, `addTeardown()` runs teardowns synchronously
+// in subscriber-order, which goes from downstream->upstream.
+test(() => {
+ const results = [];
+ const upstream = new Observable(subscriber => {
+ subscriber.addTeardown(
+ () => results.push(`upstream teardown. reason: ${subscriber.signal.reason}`));
+ });
+ const middle = new Observable(subscriber => {
+ subscriber.addTeardown(
+ () => results.push(`middle teardown. reason: ${subscriber.signal.reason}`));
+ upstream.subscribe({}, {signal: subscriber.signal});
+ });
+ const downstream = new Observable(subscriber => {
+ subscriber.addTeardown(
+ () => results.push(`downstream teardown. reason: ${subscriber.signal.reason}`));
+ middle.subscribe({}, {signal: subscriber.signal});
+ });
+
+ downstream.subscribe({}, {signal: AbortSignal.abort('Initial abort')});
+ assert_array_equals(results, [
+ "downstream teardown. reason: Initial abort",
+ "middle teardown. reason: Initial abort",
+ "upstream teardown. reason: Initial abort",
+ ]);
+}, "Teardowns are called in downstream->upstream order on " +
+ "consumer-initiated unsubscription with pre-aborted Signal");
+
+// Producer-initiated unsubscription test, capturing the ordering of abort events and teardowns.
+test(() => {
+ const results = [];
+
+ const source = new Observable(subscriber => {
+ subscriber.addTeardown(() => results.push('source teardown'));
+ subscriber.signal.addEventListener('abort',
+ e => results.push('source abort event'));
+ });
+
+ const middle = new Observable(subscriber => {
+ subscriber.addTeardown(() => results.push('middle teardown'));
+ subscriber.signal.addEventListener('abort',
+ e => results.push('middle abort event'));
+
+ source.subscribe(() => {}, {signal: subscriber.signal});
+ });
+
+ let innerSubscriber = null;
+ const downstream = new Observable(subscriber => {
+ innerSubscriber = subscriber;
+ subscriber.addTeardown(() => results.push('downstream teardown'));
+ subscriber.signal.addEventListener('abort',
+ e => results.push('downstream abort event'));
+
+ middle.subscribe(() => {}, {signal: subscriber.signal});
+ });
+
+ downstream.subscribe();
+
+ // Trigger a producer-initiated unsubscription from the most-downstream Observable.
+ innerSubscriber.complete();
+
+ assert_array_equals(results, [
+ 'source abort event',
+ 'source teardown',
+ 'middle abort event',
+ 'middle teardown',
+ 'downstream abort event',
+ 'downstream teardown',
+ ]);
+}, "Producer-initiated unsubscription in a downstream Observable fires abort " +
+ "events before each teardown, in downstream->upstream order");
+
test(t => {
let innerSubscriber = null;
const source = new Observable(subscriber => {
diff --git a/tests/wpt/tests/dom/observable/tentative/observable-filter.any.js b/tests/wpt/tests/dom/observable/tentative/observable-filter.any.js
index 3c1a7d78248..419d59ed8a8 100644
--- a/tests/wpt/tests/dom/observable/tentative/observable-filter.any.js
+++ b/tests/wpt/tests/dom/observable/tentative/observable-filter.any.js
@@ -30,6 +30,7 @@ test(() => {
subscriber.next(1);
assert_true(teardownCalled, "Teardown called once map unsubscribes due to error");
assert_false(subscriber.active, "Unsubscription makes Subscriber inactive");
+ results.push(subscriber.signal.reason);
subscriber.next(2);
subscriber.complete();
});
@@ -44,7 +45,7 @@ test(() => {
complete: () => results.push("complete"),
});
- assert_array_equals(results, [error]);
+ assert_array_equals(results, [error, error]);
}, "filter(): Errors thrown in filter predicate are emitted to Observer error() handler");
test(() => {
@@ -100,7 +101,7 @@ test(() => {
});
assert_array_equals(results,
- ['source teardown', 'source abort event', 'filter observable complete']);
+ ['source abort event', 'source teardown', 'filter observable complete']);
}, "filter(): Upon source completion, source Observable teardown sequence " +
"happens after downstream filter complete() is called");
diff --git a/tests/wpt/tests/dom/observable/tentative/observable-first.any.js b/tests/wpt/tests/dom/observable/tentative/observable-first.any.js
index 7c99066dc22..d4738d7478b 100644
--- a/tests/wpt/tests/dom/observable/tentative/observable-first.any.js
+++ b/tests/wpt/tests/dom/observable/tentative/observable-first.any.js
@@ -94,8 +94,8 @@ promise_test(async () => {
"calling first",
"source subscribe",
"before source next 1",
- "source teardown",
"source abort",
+ "source teardown",
"after source next 1"
], "Array values after first() is called");
@@ -106,8 +106,8 @@ promise_test(async () => {
"calling first",
"source subscribe",
"before source next 1",
- "source teardown",
"source abort",
+ "source teardown",
"after source next 1",
"first resolved with: 1",
], "Array values after Promise is awaited");
diff --git a/tests/wpt/tests/dom/observable/tentative/observable-last.any.js b/tests/wpt/tests/dom/observable/tentative/observable-last.any.js
index cd39a3700a2..064a781cada 100644
--- a/tests/wpt/tests/dom/observable/tentative/observable-last.any.js
+++ b/tests/wpt/tests/dom/observable/tentative/observable-last.any.js
@@ -91,8 +91,8 @@ promise_test(async () => {
"before source next 1",
"after source next 1",
"before source complete",
- "source teardown",
"source abort",
+ "source teardown",
"after source complete",
], "Array values after last() is called");
@@ -105,8 +105,8 @@ promise_test(async () => {
"before source next 1",
"after source next 1",
"before source complete",
- "source teardown",
"source abort",
+ "source teardown",
"after source complete",
"last resolved with: 1",
], "Array values after Promise is awaited");
diff --git a/tests/wpt/tests/dom/observable/tentative/observable-map.any.js b/tests/wpt/tests/dom/observable/tentative/observable-map.any.js
index 275505fb5d0..a61c818bc16 100644
--- a/tests/wpt/tests/dom/observable/tentative/observable-map.any.js
+++ b/tests/wpt/tests/dom/observable/tentative/observable-map.any.js
@@ -132,7 +132,7 @@ test(() => {
});
assert_array_equals(results,
- ['source teardown', 'source abort event', 'map observable complete']);
+ ['source abort event', 'source teardown', 'map observable complete']);
}, "map(): Upon source completion, source Observable teardown sequence " +
"happens before downstream mapper complete() is called");
diff --git a/tests/wpt/tests/dom/observable/tentative/observable-switchMap.any.js b/tests/wpt/tests/dom/observable/tentative/observable-switchMap.any.js
index 836a39a68e0..577ce2b748c 100644
--- a/tests/wpt/tests/dom/observable/tentative/observable-switchMap.any.js
+++ b/tests/wpt/tests/dom/observable/tentative/observable-switchMap.any.js
@@ -212,10 +212,10 @@ test(() => {
ac.abort();
assert_array_equals(results, [
- "source teardown",
"source onabort",
- "inner teardown",
+ "source teardown",
"inner onabort",
+ "inner teardown",
], "Unsubscription order is correct");
}, "switchMap(): should unsubscribe in the correct order when user aborts " +
"the subscription");
diff --git a/tests/wpt/tests/dom/observable/tentative/observable-takeUntil.any.js b/tests/wpt/tests/dom/observable/tentative/observable-takeUntil.any.js
index 2895dd31e3f..f2e99b8cbec 100644
--- a/tests/wpt/tests/dom/observable/tentative/observable-takeUntil.any.js
+++ b/tests/wpt/tests/dom/observable/tentative/observable-takeUntil.any.js
@@ -179,10 +179,10 @@ promise_test(async t => {
assert_array_equals(results, [
"notifier subscribed",
"source subscribed",
- "notifier teardown",
"notifier signal abort",
- "source teardown",
+ "notifier teardown",
"source signal abort",
+ "source teardown",
"complete callback",
]);
}, "takeUntil: notifier next() unsubscribes from notifier & source observable");
@@ -235,10 +235,10 @@ promise_test(async t => {
assert_array_equals(results, [
"notifier subscribed",
"source subscribed",
- "notifier teardown",
"notifier signal abort",
+ "notifier teardown",
+ "source signal abort",
"source teardown",
- "source signal abort"
]);
}, "takeUntil()'s AbortSignal unsubscribes from notifier & source observable");
diff --git a/tests/wpt/tests/dom/observable/tentative/observable-toArray.any.js b/tests/wpt/tests/dom/observable/tentative/observable-toArray.any.js
index 9e6e3abee56..582bc67453e 100644
--- a/tests/wpt/tests/dom/observable/tentative/observable-toArray.any.js
+++ b/tests/wpt/tests/dom/observable/tentative/observable-toArray.any.js
@@ -152,9 +152,9 @@ promise_test(async () => {
assert_array_equals(results, [
"Subscribed. active: true",
- "Outer signal abort event",
- "Teardown",
"Inner signal abort event",
+ "Teardown",
+ "Outer signal abort event",
], "Events and teardowns are fired in the right ordered");
// Everything microtask above should be queued up by now, so queue one more
@@ -163,12 +163,12 @@ promise_test(async () => {
await Promise.resolve();
assert_array_equals(results, [
"Subscribed. active: true",
- "Outer signal abort event",
- "Teardown",
"Inner signal abort event",
- "Outer signal Promise",
- "Teardown Promise",
+ "Teardown",
+ "Outer signal abort event",
"Inner signal Promise",
+ "Teardown Promise",
+ "Outer signal Promise",
], "Promises resolve in the right order");
}, "Operator Promise abort ordering");
diff --git a/tests/wpt/tests/fedcm/META.yml b/tests/wpt/tests/fedcm/META.yml
new file mode 100644
index 00000000000..40b5b106f62
--- /dev/null
+++ b/tests/wpt/tests/fedcm/META.yml
@@ -0,0 +1,3 @@
+spec: https://fedidcg.github.io/FedCM/
+suggested_reviewers:
+ - npm
diff --git a/tests/wpt/tests/credential-management/fedcm-abort.https.html b/tests/wpt/tests/fedcm/fedcm-abort.https.html
index 0f03bff832f..0f03bff832f 100644
--- a/tests/wpt/tests/credential-management/fedcm-abort.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-abort.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-after-abort.https.html b/tests/wpt/tests/fedcm/fedcm-after-abort.https.html
index 3c2f981e82f..3c2f981e82f 100644
--- a/tests/wpt/tests/credential-management/fedcm-after-abort.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-after-abort.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-authz/fedcm-continue-on-disallowed.https.html b/tests/wpt/tests/fedcm/fedcm-authz/fedcm-continue-on-disallowed.https.html
index fcda3a3dd59..fcda3a3dd59 100644
--- a/tests/wpt/tests/credential-management/fedcm-authz/fedcm-continue-on-disallowed.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-authz/fedcm-continue-on-disallowed.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-authz/fedcm-continue-on-with-account.https.html b/tests/wpt/tests/fedcm/fedcm-authz/fedcm-continue-on-with-account.https.html
index 5bd8ef34fe8..5bd8ef34fe8 100644
--- a/tests/wpt/tests/credential-management/fedcm-authz/fedcm-continue-on-with-account.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-authz/fedcm-continue-on-with-account.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-authz/fedcm-continue-on.https.html b/tests/wpt/tests/fedcm/fedcm-authz/fedcm-continue-on.https.html
index c7da5384af4..c7da5384af4 100644
--- a/tests/wpt/tests/credential-management/fedcm-authz/fedcm-continue-on.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-authz/fedcm-continue-on.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-authz/fedcm-disclosure-text-shown.https.html b/tests/wpt/tests/fedcm/fedcm-authz/fedcm-disclosure-text-shown.https.html
index 513ef258e18..513ef258e18 100644
--- a/tests/wpt/tests/credential-management/fedcm-authz/fedcm-disclosure-text-shown.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-authz/fedcm-disclosure-text-shown.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-authz/fedcm-userinfo-after-resolve.https.html b/tests/wpt/tests/fedcm/fedcm-authz/fedcm-userinfo-after-resolve.https.html
index 0521f4a2ab5..e92f4898863 100644
--- a/tests/wpt/tests/credential-management/fedcm-authz/fedcm-userinfo-after-resolve.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-authz/fedcm-userinfo-after-resolve.https.html
@@ -32,7 +32,7 @@ fedcm_test(async t => {
assert_equals(cred.token, "account=1234");
const iframe_in_idp_scope = `${alt_manifest_origin}/\
-credential-management/support/fedcm/userinfo-iframe.html`;
+fedcm/support/fedcm/userinfo-iframe.html`;
const message = await createIframeWithPermissionPolicyAndWaitForMessage(t, iframe_in_idp_scope);
assert_equals(message.result, "Pass");
assert_equals(message.numAccounts, 1);
diff --git a/tests/wpt/tests/credential-management/fedcm-authz/resolve-after-preventsilentaccess.https.html b/tests/wpt/tests/fedcm/fedcm-authz/resolve-after-preventsilentaccess.https.html
index 7223a722fe2..7223a722fe2 100644
--- a/tests/wpt/tests/credential-management/fedcm-authz/resolve-after-preventsilentaccess.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-authz/resolve-after-preventsilentaccess.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-auto-reauthn-without-approved-clients.https.html b/tests/wpt/tests/fedcm/fedcm-auto-reauthn-without-approved-clients.https.html
index fb93cb632db..fb93cb632db 100644
--- a/tests/wpt/tests/credential-management/fedcm-auto-reauthn-without-approved-clients.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-auto-reauthn-without-approved-clients.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-auto-selected-flag.https.html b/tests/wpt/tests/fedcm/fedcm-auto-selected-flag.https.html
index d06aba73bc1..d06aba73bc1 100644
--- a/tests/wpt/tests/credential-management/fedcm-auto-selected-flag.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-auto-selected-flag.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-basic.https.html b/tests/wpt/tests/fedcm/fedcm-basic.https.html
index 3d20f4cfb74..3d20f4cfb74 100644
--- a/tests/wpt/tests/credential-management/fedcm-basic.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-basic.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-button-and-other-account/fedcm-button-mode-basics.tentative.https.html b/tests/wpt/tests/fedcm/fedcm-button-and-other-account/fedcm-button-mode-basics.tentative.https.html
index a71e2621352..a71e2621352 100644
--- a/tests/wpt/tests/credential-management/fedcm-button-and-other-account/fedcm-button-mode-basics.tentative.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-button-and-other-account/fedcm-button-mode-basics.tentative.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-button-and-other-account/fedcm-button-mode-priority.tentative.https.html b/tests/wpt/tests/fedcm/fedcm-button-and-other-account/fedcm-button-mode-priority.tentative.https.html
index b71e84db47e..b71e84db47e 100644
--- a/tests/wpt/tests/credential-management/fedcm-button-and-other-account/fedcm-button-mode-priority.tentative.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-button-and-other-account/fedcm-button-mode-priority.tentative.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-button-and-other-account/fedcm-use-other-account-button-flow.tentative.https.html b/tests/wpt/tests/fedcm/fedcm-button-and-other-account/fedcm-use-other-account-button-flow.tentative.https.html
index 7a3f266b24b..7a3f266b24b 100644
--- a/tests/wpt/tests/credential-management/fedcm-button-and-other-account/fedcm-use-other-account-button-flow.tentative.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-button-and-other-account/fedcm-use-other-account-button-flow.tentative.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-button-and-other-account/fedcm-use-other-account.tentative.https.html b/tests/wpt/tests/fedcm/fedcm-button-and-other-account/fedcm-use-other-account.tentative.https.html
index 66311740124..66311740124 100644
--- a/tests/wpt/tests/credential-management/fedcm-button-and-other-account/fedcm-use-other-account.tentative.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-button-and-other-account/fedcm-use-other-account.tentative.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-client-metadata-not-cached.https.html b/tests/wpt/tests/fedcm/fedcm-client-metadata-not-cached.https.html
index 79171bf6343..79171bf6343 100644
--- a/tests/wpt/tests/credential-management/fedcm-client-metadata-not-cached.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-client-metadata-not-cached.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-context.https.html b/tests/wpt/tests/fedcm/fedcm-context.https.html
index f235437b789..f235437b789 100644
--- a/tests/wpt/tests/credential-management/fedcm-context.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-context.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-cross-origin-policy.https.html b/tests/wpt/tests/fedcm/fedcm-cross-origin-policy.https.html
index 1e3b4c71a83..1e3b4c71a83 100644
--- a/tests/wpt/tests/credential-management/fedcm-cross-origin-policy.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-cross-origin-policy.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-cross-origin-policy.https.html.sub.headers b/tests/wpt/tests/fedcm/fedcm-cross-origin-policy.https.html.sub.headers
index 32523a69788..32523a69788 100644
--- a/tests/wpt/tests/credential-management/fedcm-cross-origin-policy.https.html.sub.headers
+++ b/tests/wpt/tests/fedcm/fedcm-cross-origin-policy.https.html.sub.headers
diff --git a/tests/wpt/tests/credential-management/fedcm-csp.https.html b/tests/wpt/tests/fedcm/fedcm-csp.https.html
index c9a2456e4d0..c9a2456e4d0 100644
--- a/tests/wpt/tests/credential-management/fedcm-csp.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-csp.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-csp.https.html.sub.headers b/tests/wpt/tests/fedcm/fedcm-csp.https.html.sub.headers
index c1e6fd6c4c2..c1e6fd6c4c2 100644
--- a/tests/wpt/tests/credential-management/fedcm-csp.https.html.sub.headers
+++ b/tests/wpt/tests/fedcm/fedcm-csp.https.html.sub.headers
diff --git a/tests/wpt/tests/credential-management/fedcm-disconnect-errors.https.html b/tests/wpt/tests/fedcm/fedcm-disconnect-errors.https.html
index 4d5fb0a457c..4d5fb0a457c 100644
--- a/tests/wpt/tests/credential-management/fedcm-disconnect-errors.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-disconnect-errors.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-disconnect-iframe.sub.https.html b/tests/wpt/tests/fedcm/fedcm-disconnect-iframe.sub.https.html
index 3d31be60b18..275a4a55c5e 100644
--- a/tests/wpt/tests/credential-management/fedcm-disconnect-iframe.sub.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-disconnect-iframe.sub.https.html
@@ -33,14 +33,14 @@ fedcm_test(async t => {
fedcm_test(async t => {
const message = await createIframeAndWaitForMessage(t,
- 'https://{{hosts[alt][]}}:{{ports[https][0]}}/credential-management/support/fedcm/disconnect-iframe.html?skip_get');
+ 'https://{{hosts[alt][]}}:{{ports[https][0]}}/fedcm/support/fedcm/disconnect-iframe.html?skip_get');
assert_equals(message.result, "Failed disconnect");
assert_equals(message.errorType, "NotAllowedError");
}, 'Cross-origin iframe fails disconnect() without explicit identity-credentials-get');
fedcm_test(async t => {
const message = await createIframeAndWaitForMessage(t,
- 'https://{{hosts[alt][]}}:{{ports[https][0]}}/credential-management/support/fedcm/disconnect-iframe.html',
+ 'https://{{hosts[alt][]}}:{{ports[https][0]}}/fedcm/support/fedcm/disconnect-iframe.html',
/*allow=*/true);
assert_equals(message.result, "Pass");
}, 'Cross-origin iframe can disconnect with explicit identity-credentials-get');
diff --git a/tests/wpt/tests/credential-management/fedcm-disconnect.sub.https.html b/tests/wpt/tests/fedcm/fedcm-disconnect.sub.https.html
index 2ea2d4a2599..2ea2d4a2599 100644
--- a/tests/wpt/tests/credential-management/fedcm-disconnect.sub.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-disconnect.sub.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-domainhint.https.html b/tests/wpt/tests/fedcm/fedcm-domainhint.https.html
index 20b4569a05e..20b4569a05e 100644
--- a/tests/wpt/tests/credential-management/fedcm-domainhint.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-domainhint.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-endpoint-redirects.https.html b/tests/wpt/tests/fedcm/fedcm-endpoint-redirects.https.html
index 71dbce03267..71dbce03267 100644
--- a/tests/wpt/tests/credential-management/fedcm-endpoint-redirects.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-endpoint-redirects.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-error-basic.https.html b/tests/wpt/tests/fedcm/fedcm-error-basic.https.html
index 8a2d39cabaa..8a2d39cabaa 100644
--- a/tests/wpt/tests/credential-management/fedcm-error-basic.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-error-basic.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-identity-assertion-nocors.https.html b/tests/wpt/tests/fedcm/fedcm-identity-assertion-nocors.https.html
index 612387b4a0d..612387b4a0d 100644
--- a/tests/wpt/tests/credential-management/fedcm-identity-assertion-nocors.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-identity-assertion-nocors.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-iframe.https.html b/tests/wpt/tests/fedcm/fedcm-iframe.https.html
index 6a9bec677cc..6a9bec677cc 100644
--- a/tests/wpt/tests/credential-management/fedcm-iframe.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-iframe.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-login-status-unknown.https.html b/tests/wpt/tests/fedcm/fedcm-login-status-unknown.https.html
index d542524c884..d542524c884 100644
--- a/tests/wpt/tests/credential-management/fedcm-login-status-unknown.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-login-status-unknown.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-login-status/confirm-idp-login.https.html b/tests/wpt/tests/fedcm/fedcm-login-status/confirm-idp-login.https.html
index 0f8df72b615..0f8df72b615 100644
--- a/tests/wpt/tests/credential-management/fedcm-login-status/confirm-idp-login.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-login-status/confirm-idp-login.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-login-status/cross-origin-status.https.html b/tests/wpt/tests/fedcm/fedcm-login-status/cross-origin-status.https.html
index f32e18d40ed..dc0ff948904 100644
--- a/tests/wpt/tests/credential-management/fedcm-login-status/cross-origin-status.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-login-status/cross-origin-status.https.html
@@ -18,7 +18,7 @@ import {fedcm_test,
open_and_wait_for_popup,
mark_signed_out} from '../support/fedcm-helper.sub.js';
-const path = '/credential-management/support/'
+const path = '/fedcm/support/'
const url_prefix = alt_manifest_origin + path;
const same_site_url_prefix = same_site_manifest_origin + path;
@@ -76,7 +76,7 @@ fedcm_test(async t => {
fedcm_test(async t => {
await mark_signed_out(alt_manifest_origin);
- await open_and_wait_for_popup(alt_manifest_origin, "/credential-management/support/fencedframe-mark-signedin.html");
+ await open_and_wait_for_popup(alt_manifest_origin, "/fedcm/support/fencedframe-mark-signedin.html");
const config = alt_request_options_with_mediation_required();
const result = navigator.credentials.get(config);
diff --git a/tests/wpt/tests/credential-management/fedcm-login-status/logged-out.https.html b/tests/wpt/tests/fedcm/fedcm-login-status/logged-out.https.html
index 09750ff0968..4d11e64ff37 100644
--- a/tests/wpt/tests/credential-management/fedcm-login-status/logged-out.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-login-status/logged-out.https.html
@@ -39,7 +39,7 @@ fedcm_test(async t => {
await mark_signed_out(alt_manifest_origin);
const iframe_in_idp_scope = `${alt_manifest_origin}/\
-credential-management/support/fedcm/userinfo-iframe.html`;
+fedcm/support/fedcm/userinfo-iframe.html`;
const message = await createIframeWithPermissionPolicyAndWaitForMessage(t, iframe_in_idp_scope);
assert_equals(message.result, "Fail");
diff --git a/tests/wpt/tests/credential-management/fedcm-loginhint.https.html b/tests/wpt/tests/fedcm/fedcm-loginhint.https.html
index fe35007a87d..fe35007a87d 100644
--- a/tests/wpt/tests/credential-management/fedcm-loginhint.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-loginhint.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-manifest-not-in-list.https.html b/tests/wpt/tests/fedcm/fedcm-manifest-not-in-list.https.html
index 087af384b15..087af384b15 100644
--- a/tests/wpt/tests/credential-management/fedcm-manifest-not-in-list.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-manifest-not-in-list.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-multi-idp/fedcm-multi-idp-abort.https.html b/tests/wpt/tests/fedcm/fedcm-multi-idp/fedcm-multi-idp-abort.https.html
index 712a7b6a349..712a7b6a349 100644
--- a/tests/wpt/tests/credential-management/fedcm-multi-idp/fedcm-multi-idp-abort.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-multi-idp/fedcm-multi-idp-abort.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-multi-idp/fedcm-multi-idp-basic.https.html b/tests/wpt/tests/fedcm/fedcm-multi-idp/fedcm-multi-idp-basic.https.html
index d855e0ad8dc..d855e0ad8dc 100644
--- a/tests/wpt/tests/credential-management/fedcm-multi-idp/fedcm-multi-idp-basic.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-multi-idp/fedcm-multi-idp-basic.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-multi-idp/fedcm-multi-idp-context.https.html b/tests/wpt/tests/fedcm/fedcm-multi-idp/fedcm-multi-idp-context.https.html
index 1bc3eb1f562..1bc3eb1f562 100644
--- a/tests/wpt/tests/credential-management/fedcm-multi-idp/fedcm-multi-idp-context.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-multi-idp/fedcm-multi-idp-context.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-multi-idp/fedcm-multi-idp-mediation-optional.https.html b/tests/wpt/tests/fedcm/fedcm-multi-idp/fedcm-multi-idp-mediation-optional.https.html
index 1a819efb314..1a819efb314 100644
--- a/tests/wpt/tests/credential-management/fedcm-multi-idp/fedcm-multi-idp-mediation-optional.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-multi-idp/fedcm-multi-idp-mediation-optional.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-multi-idp/fedcm-multi-idp-mediation-silent.https.html b/tests/wpt/tests/fedcm/fedcm-multi-idp/fedcm-multi-idp-mediation-silent.https.html
index d47d4898c7d..d47d4898c7d 100644
--- a/tests/wpt/tests/credential-management/fedcm-multi-idp/fedcm-multi-idp-mediation-silent.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-multi-idp/fedcm-multi-idp-mediation-silent.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-no-login-url.https.html b/tests/wpt/tests/fedcm/fedcm-no-login-url.https.html
index 94592d2dbfb..94592d2dbfb 100644
--- a/tests/wpt/tests/credential-management/fedcm-no-login-url.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-no-login-url.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-nonce-is-optional.https.html b/tests/wpt/tests/fedcm/fedcm-nonce-is-optional.https.html
index dafd6c9e983..dafd6c9e983 100644
--- a/tests/wpt/tests/credential-management/fedcm-nonce-is-optional.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-nonce-is-optional.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-not-observed-by-service-worker.https.html b/tests/wpt/tests/fedcm/fedcm-not-observed-by-service-worker.https.html
index 072d6696656..a6537add106 100644
--- a/tests/wpt/tests/credential-management/fedcm-not-observed-by-service-worker.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-not-observed-by-service-worker.https.html
@@ -26,7 +26,7 @@ function loadUrlInIframe(url) {
fedcm_test(async t => {
const service_worker_url = 'support/fedcm/intercept_service_worker.js';
- const sw_scope_url = '/credential-management/support/fedcm/';
+ const sw_scope_url = '/fedcm/support/fedcm/';
// URL for querying number of page loads observed by service worker.
const query_sw_intercepts_url = 'support/fedcm/query_service_worker_intercepts.html';
const page_in_sw_scope_url = 'support/fedcm/simple.html';
diff --git a/tests/wpt/tests/credential-management/fedcm-opaque-rp-origin.https.html b/tests/wpt/tests/fedcm/fedcm-opaque-rp-origin.https.html
index 228646e0fee..228646e0fee 100644
--- a/tests/wpt/tests/credential-management/fedcm-opaque-rp-origin.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-opaque-rp-origin.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-opaque-rp-origin.https.html.headers b/tests/wpt/tests/fedcm/fedcm-opaque-rp-origin.https.html.headers
index 9850d21f3c6..9850d21f3c6 100644
--- a/tests/wpt/tests/credential-management/fedcm-opaque-rp-origin.https.html.headers
+++ b/tests/wpt/tests/fedcm/fedcm-opaque-rp-origin.https.html.headers
diff --git a/tests/wpt/tests/credential-management/fedcm-pending-call-rejected.https.html b/tests/wpt/tests/fedcm/fedcm-pending-call-rejected.https.html
index bb9f885a8a1..bb9f885a8a1 100644
--- a/tests/wpt/tests/credential-management/fedcm-pending-call-rejected.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-pending-call-rejected.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-pending-disconnect.https.html b/tests/wpt/tests/fedcm/fedcm-pending-disconnect.https.html
index 1b60acc1088..1b60acc1088 100644
--- a/tests/wpt/tests/credential-management/fedcm-pending-disconnect.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-pending-disconnect.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-pending-userinfo.https.html b/tests/wpt/tests/fedcm/fedcm-pending-userinfo.https.html
index 0ecae3e80a4..1c85d4dc57d 100644
--- a/tests/wpt/tests/credential-management/fedcm-pending-userinfo.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-pending-userinfo.https.html
@@ -29,7 +29,7 @@ fedcm_test(async t => {
assert_equals(cred.token, "token");
const iframe_in_idp_scope = `${alt_manifest_origin}/\
-credential-management/support/fedcm/pending-userinfo-iframe.html`;
+fedcm/support/fedcm/pending-userinfo-iframe.html`;
const message = await createIframeWithPermissionPolicyAndWaitForMessage(t, iframe_in_idp_scope);
assert_equals(message, "Pass");
}, 'Test basic User InFo API flow');
diff --git a/tests/wpt/tests/credential-management/fedcm-register/fedcm-no-registered-idps.https.html b/tests/wpt/tests/fedcm/fedcm-register/fedcm-no-registered-idps.https.html
index 7be2d397e68..7be2d397e68 100644
--- a/tests/wpt/tests/credential-management/fedcm-register/fedcm-no-registered-idps.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-register/fedcm-no-registered-idps.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-reject-invalid-responses.https.html b/tests/wpt/tests/fedcm/fedcm-reject-invalid-responses.https.html
index f450d568249..f450d568249 100644
--- a/tests/wpt/tests/credential-management/fedcm-reject-invalid-responses.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-reject-invalid-responses.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-returning-account-auto-reauthn.https.html b/tests/wpt/tests/fedcm/fedcm-returning-account-auto-reauthn.https.html
index c9fe10a485f..c9fe10a485f 100644
--- a/tests/wpt/tests/credential-management/fedcm-returning-account-auto-reauthn.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-returning-account-auto-reauthn.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-same-site-none/fedcm-same-site-none.https.html b/tests/wpt/tests/fedcm/fedcm-same-site-none/fedcm-same-site-none.https.html
index d3d20ea9df2..d3d20ea9df2 100644
--- a/tests/wpt/tests/credential-management/fedcm-same-site-none/fedcm-same-site-none.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-same-site-none/fedcm-same-site-none.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-store.https.html b/tests/wpt/tests/fedcm/fedcm-store.https.html
index d1e6ef464c4..d1e6ef464c4 100644
--- a/tests/wpt/tests/credential-management/fedcm-store.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-store.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-token-returned-with-http-error.https.html b/tests/wpt/tests/fedcm/fedcm-token-returned-with-http-error.https.html
index 7c7687f00f3..7c7687f00f3 100644
--- a/tests/wpt/tests/credential-management/fedcm-token-returned-with-http-error.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-token-returned-with-http-error.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-too-many-disconnect-calls.https.html b/tests/wpt/tests/fedcm/fedcm-too-many-disconnect-calls.https.html
index eb87c2377a9..eb87c2377a9 100644
--- a/tests/wpt/tests/credential-management/fedcm-too-many-disconnect-calls.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-too-many-disconnect-calls.https.html
diff --git a/tests/wpt/tests/credential-management/fedcm-userinfo.https.html b/tests/wpt/tests/fedcm/fedcm-userinfo.https.html
index d460d828452..9f7be9fbbf3 100644
--- a/tests/wpt/tests/credential-management/fedcm-userinfo.https.html
+++ b/tests/wpt/tests/fedcm/fedcm-userinfo.https.html
@@ -29,7 +29,7 @@ fedcm_test(async t => {
assert_equals(cred.token, "token");
const iframe_in_idp_scope = `${alt_manifest_origin}/\
-credential-management/support/fedcm/userinfo-iframe.html`;
+fedcm/support/fedcm/userinfo-iframe.html`;
const message = await createIframeWithPermissionPolicyAndWaitForMessage(t, iframe_in_idp_scope);
assert_equals(message.result, "Pass");
assert_equals(message.numAccounts, 1);
@@ -54,7 +54,7 @@ fedcm_test(async t => {
try {
const manifest_path = `${alt_manifest_origin}/\
-credential-management/support/fedcm/manifest.py`;
+fedcm/support/fedcm/manifest.py`;
const user_info = await IdentityProvider.getUserInfo({
configURL: manifest_path,
// Approved client
diff --git a/tests/wpt/tests/fedcm/support/README.md b/tests/wpt/tests/fedcm/support/README.md
new file mode 100644
index 00000000000..28a6d3453bd
--- /dev/null
+++ b/tests/wpt/tests/fedcm/support/README.md
@@ -0,0 +1,20 @@
+# FedCM Testing
+
+`fedcm-mojojs-helper.js` exposes `fedcm_mojo_mock_test` which is a specialized
+`promise_test` which comes pre-setup with the appropriate mocking infrastructure
+to emulate platform federated auth backend. The mock is passed to the test
+function as the second parameter.
+
+Example usage:
+```
+<script type="module">
+ import {fedcm_mojo_mock_test} from './support/fedcm-mojojs-helper.js';
+
+ fedcm_mojo_mock_test(async (t, mock) => {
+ mock.returnToken("https://idp.test/fedcm.json", "a_token");
+ assert_equals("a_token", await navigator.credentials.get(options));
+ }, "Successfully obtaining a token using mock.");
+</script>
+```
+
+The chromium implementation uses the MojoJS shim.
diff --git a/tests/wpt/tests/fedcm/support/accounts_check_same_site_strict.py b/tests/wpt/tests/fedcm/support/accounts_check_same_site_strict.py
index 27a5d6a5b3e..7bab26d3e9b 100644
--- a/tests/wpt/tests/fedcm/support/accounts_check_same_site_strict.py
+++ b/tests/wpt/tests/fedcm/support/accounts_check_same_site_strict.py
@@ -7,6 +7,7 @@ def main(request, response):
return request_error
if request.cookies.get(b"same_site_strict") == b"1":
return (546, [], "Should not send SameSite=Strict cookies")
+ # TODO(crbug.com/350944661): We want to send these cookies.
if request.cookies.get(b"same_site_lax") == b"1":
return (547, [], "Should not send SameSite=Lax cookies")
if request.headers.get(b"Sec-Fetch-Site") != b"cross-site":
diff --git a/tests/wpt/tests/credential-management/support/fedcm-helper.sub.js b/tests/wpt/tests/fedcm/support/fedcm-helper.sub.js
index 17ed5ce4468..767c044818e 100644
--- a/tests/wpt/tests/credential-management/support/fedcm-helper.sub.js
+++ b/tests/wpt/tests/fedcm/support/fedcm-helper.sub.js
@@ -25,11 +25,10 @@ export function open_and_wait_for_popup(origin, path) {
// Set the identity provider cookie.
export function set_fedcm_cookie(host) {
if (host == undefined) {
- document.cookie = 'cookie=1; SameSite=None; Path=/credential-management/support; Secure';
document.cookie = 'cookie=1; SameSite=None; Path=/fedcm/support; Secure';
return Promise.resolve();
} else {
- return open_and_wait_for_popup(host, '/credential-management/support/set_cookie');
+ return open_and_wait_for_popup(host, '/fedcm/support/set_cookie');
}
}
@@ -39,11 +38,11 @@ export function set_alt_fedcm_cookie() {
}
export function mark_signed_in(origin = manifest_origin) {
- return open_and_wait_for_popup(origin, '/credential-management/support/mark_signedin');
+ return open_and_wait_for_popup(origin, '/fedcm/support/mark_signedin');
}
export function mark_signed_out(origin = manifest_origin) {
- return open_and_wait_for_popup(origin, '/credential-management/support/mark_signedout');
+ return open_and_wait_for_popup(origin, '/fedcm/support/mark_signedout');
}
// Returns FedCM CredentialRequestOptions for which navigator.credentials.get()
diff --git a/tests/wpt/tests/credential-management/support/fedcm-helper.sub.js.headers b/tests/wpt/tests/fedcm/support/fedcm-helper.sub.js.headers
index cb762eff806..cb762eff806 100644
--- a/tests/wpt/tests/credential-management/support/fedcm-helper.sub.js.headers
+++ b/tests/wpt/tests/fedcm/support/fedcm-helper.sub.js.headers
diff --git a/tests/wpt/tests/credential-management/support/fedcm-iframe-level2.html b/tests/wpt/tests/fedcm/support/fedcm-iframe-level2.html
index 7622d988ff2..7622d988ff2 100644
--- a/tests/wpt/tests/credential-management/support/fedcm-iframe-level2.html
+++ b/tests/wpt/tests/fedcm/support/fedcm-iframe-level2.html
diff --git a/tests/wpt/tests/credential-management/support/fedcm-iframe.html b/tests/wpt/tests/fedcm/support/fedcm-iframe.html
index ba79c4cf9e9..ba79c4cf9e9 100644
--- a/tests/wpt/tests/credential-management/support/fedcm-iframe.html
+++ b/tests/wpt/tests/fedcm/support/fedcm-iframe.html
diff --git a/tests/wpt/tests/credential-management/support/fedcm-mock.js b/tests/wpt/tests/fedcm/support/fedcm-mock.js
index 271dd9cd944..271dd9cd944 100644
--- a/tests/wpt/tests/credential-management/support/fedcm-mock.js
+++ b/tests/wpt/tests/fedcm/support/fedcm-mock.js
diff --git a/tests/wpt/tests/credential-management/support/fedcm-mojojs-helper.js b/tests/wpt/tests/fedcm/support/fedcm-mojojs-helper.js
index 40ab729b1f9..40ab729b1f9 100644
--- a/tests/wpt/tests/credential-management/support/fedcm-mojojs-helper.js
+++ b/tests/wpt/tests/fedcm/support/fedcm-mojojs-helper.js
diff --git a/tests/wpt/tests/credential-management/support/fedcm/disconnect-iframe.html b/tests/wpt/tests/fedcm/support/fedcm/disconnect-iframe.html
index f65763932b8..f65763932b8 100644
--- a/tests/wpt/tests/credential-management/support/fedcm/disconnect-iframe.html
+++ b/tests/wpt/tests/fedcm/support/fedcm/disconnect-iframe.html
diff --git a/tests/wpt/tests/credential-management/support/fedcm/intercept_service_worker.js b/tests/wpt/tests/fedcm/support/fedcm/intercept_service_worker.js
index fd0bb71a0c7..b458c973be3 100644
--- a/tests/wpt/tests/credential-management/support/fedcm/intercept_service_worker.js
+++ b/tests/wpt/tests/fedcm/support/fedcm/intercept_service_worker.js
@@ -4,8 +4,7 @@ self.addEventListener('fetch', event => {
const url = event.request.url;
if (url.indexOf('query_service_worker_intercepts.html') != -1) {
event.respondWith(new Response(num_overridden));
- } else if (url.indexOf('credential-management/support' ||
- url.indexOf('fedcm/support') != -1)) {
+ } else if (url.indexOf('fedcm/support') != -1) {
++num_overridden;
}
});
diff --git a/tests/wpt/tests/credential-management/support/fedcm/pending-userinfo-iframe.html b/tests/wpt/tests/fedcm/support/fedcm/pending-userinfo-iframe.html
index da2cd26066a..da2cd26066a 100644
--- a/tests/wpt/tests/credential-management/support/fedcm/pending-userinfo-iframe.html
+++ b/tests/wpt/tests/fedcm/support/fedcm/pending-userinfo-iframe.html
diff --git a/tests/wpt/tests/credential-management/support/fedcm/simple.html b/tests/wpt/tests/fedcm/support/fedcm/simple.html
index d62419ce8a0..d62419ce8a0 100644
--- a/tests/wpt/tests/credential-management/support/fedcm/simple.html
+++ b/tests/wpt/tests/fedcm/support/fedcm/simple.html
diff --git a/tests/wpt/tests/credential-management/support/fedcm/userinfo-iframe.html b/tests/wpt/tests/fedcm/support/fedcm/userinfo-iframe.html
index 64d5cb83a08..64d5cb83a08 100644
--- a/tests/wpt/tests/credential-management/support/fedcm/userinfo-iframe.html
+++ b/tests/wpt/tests/fedcm/support/fedcm/userinfo-iframe.html
diff --git a/tests/wpt/tests/credential-management/support/fencedframe-mark-signedin.html b/tests/wpt/tests/fedcm/support/fencedframe-mark-signedin.html
index 681fcd67875..681fcd67875 100644
--- a/tests/wpt/tests/credential-management/support/fencedframe-mark-signedin.html
+++ b/tests/wpt/tests/fedcm/support/fencedframe-mark-signedin.html
diff --git a/tests/wpt/tests/credential-management/support/iframe-mark-signedin.html b/tests/wpt/tests/fedcm/support/iframe-mark-signedin.html
index 4ca0125cde2..4ca0125cde2 100644
--- a/tests/wpt/tests/credential-management/support/iframe-mark-signedin.html
+++ b/tests/wpt/tests/fedcm/support/iframe-mark-signedin.html
diff --git a/tests/wpt/tests/credential-management/support/mark_signedin b/tests/wpt/tests/fedcm/support/mark_signedin
index d9adcaa7629..d9adcaa7629 100644
--- a/tests/wpt/tests/credential-management/support/mark_signedin
+++ b/tests/wpt/tests/fedcm/support/mark_signedin
diff --git a/tests/wpt/tests/credential-management/support/mark_signedin.sub.headers b/tests/wpt/tests/fedcm/support/mark_signedin.sub.headers
index d560fade5a0..d560fade5a0 100644
--- a/tests/wpt/tests/credential-management/support/mark_signedin.sub.headers
+++ b/tests/wpt/tests/fedcm/support/mark_signedin.sub.headers
diff --git a/tests/wpt/tests/credential-management/support/mark_signedout b/tests/wpt/tests/fedcm/support/mark_signedout
index d9adcaa7629..d9adcaa7629 100644
--- a/tests/wpt/tests/credential-management/support/mark_signedout
+++ b/tests/wpt/tests/fedcm/support/mark_signedout
diff --git a/tests/wpt/tests/credential-management/support/mark_signedout.sub.headers b/tests/wpt/tests/fedcm/support/mark_signedout.sub.headers
index 69157b3a371..69157b3a371 100644
--- a/tests/wpt/tests/credential-management/support/mark_signedout.sub.headers
+++ b/tests/wpt/tests/fedcm/support/mark_signedout.sub.headers
diff --git a/tests/wpt/tests/credential-management/support/set_cookie b/tests/wpt/tests/fedcm/support/set_cookie
index 2c3196058a9..2c3196058a9 100644
--- a/tests/wpt/tests/credential-management/support/set_cookie
+++ b/tests/wpt/tests/fedcm/support/set_cookie
diff --git a/tests/wpt/tests/credential-management/support/set_cookie.headers b/tests/wpt/tests/fedcm/support/set_cookie.headers
index df223115a7f..df223115a7f 100644
--- a/tests/wpt/tests/credential-management/support/set_cookie.headers
+++ b/tests/wpt/tests/fedcm/support/set_cookie.headers
diff --git a/tests/wpt/tests/fedcm/support/token_check_same_site_strict.py b/tests/wpt/tests/fedcm/support/token_check_same_site_strict.py
index f030b9b6fd5..20bdc594877 100644
--- a/tests/wpt/tests/fedcm/support/token_check_same_site_strict.py
+++ b/tests/wpt/tests/fedcm/support/token_check_same_site_strict.py
@@ -7,6 +7,7 @@ def main(request, response):
return request_error
if request.cookies.get(b"same_site_strict") == b"1":
return (546, [], "Should not send SameSite=Strict cookies")
+ # TODO(crbug.com/350944661): We want to send these cookies.
if request.cookies.get(b"same_site_lax") == b"1":
return (547, [], "Should not send SameSite=Lax cookies")
diff --git a/tests/wpt/tests/fetch/metadata/resources/post-to-owner.py b/tests/wpt/tests/fetch/metadata/resources/post-to-owner.py
index 256dd6e49dc..2d4896867bb 100644
--- a/tests/wpt/tests/fetch/metadata/resources/post-to-owner.py
+++ b/tests/wpt/tests/fetch/metadata/resources/post-to-owner.py
@@ -7,30 +7,20 @@ def main(request, response):
(b"Content-Type", b"text/html"),
(b"Cache-Control", b"no-cache, no-store, must-revalidate")
]
- key = request.GET.first(b"key", None)
-
- # We serialize the key into JSON, so have to decode it first.
- if key is not None:
- key = key.decode('utf-8')
body = u"""
<!DOCTYPE html>
- <script src="/portals/resources/stash-utils.sub.js"></script>
<script>
var data = %s;
if (window.opener)
window.opener.postMessage(data, "*");
if (window.top != window)
window.top.postMessage(data, "*");
-
- const key = %s;
- if (key)
- StashUtils.putValue(key, data);
</script>
""" % (json.dumps({
u"dest": isomorphic_decode(request.headers.get(b"sec-fetch-dest", b"")),
u"mode": isomorphic_decode(request.headers.get(b"sec-fetch-mode", b"")),
u"site": isomorphic_decode(request.headers.get(b"sec-fetch-site", b"")),
u"user": isomorphic_decode(request.headers.get(b"sec-fetch-user", b"")),
- }), json.dumps(key))
+ }))
return headers, body
diff --git a/tests/wpt/tests/geolocation/PositionOptions.https.html b/tests/wpt/tests/geolocation/PositionOptions.https.html
index e54e8679aa9..73d84738bd9 100644
--- a/tests/wpt/tests/geolocation/PositionOptions.https.html
+++ b/tests/wpt/tests/geolocation/PositionOptions.https.html
@@ -18,23 +18,6 @@
const invalidValues = ["boom", 321, -Infinity, { foo: 5 }];
promise_test(async (t) => {
- for (const enableHighAccuracy of invalidValues) {
- navigator.geolocation.getCurrentPosition(() => {}, null, {
- enableHighAccuracy,
- });
- }
- }, "Call getCurrentPosition with wrong type for enableHighAccuracy. No exception expected.");
-
- promise_test(async (t) => {
- for (const enableHighAccuracy of invalidValues) {
- const id = navigator.geolocation.watchPosition(() => {}, null, {
- enableHighAccuracy,
- });
- navigator.geolocation.clearWatch(id);
- }
- }, "Call watchPosition with wrong type for enableHighAccuracy. No exception expected.");
-
- promise_test(async (t) => {
const error = await new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(reject, resolve, {
timeout: 0,
@@ -79,4 +62,22 @@
assert_equals(error.code, GeolocationPositionError.TIMEOUT);
navigator.geolocation.clearWatch(watchId);
}, "Check that a negative timeout and maxAge values are clamped to 0 (watchPosition)");
+
+ promise_test(async (t) => {
+ for (const enableHighAccuracy of invalidValues) {
+ navigator.geolocation.getCurrentPosition(() => {}, null, {
+ enableHighAccuracy,
+ });
+ }
+ }, "Call getCurrentPosition with wrong type for enableHighAccuracy. No exception expected.");
+
+ promise_test(async (t) => {
+ for (const enableHighAccuracy of invalidValues) {
+ const id = navigator.geolocation.watchPosition(() => {}, null, {
+ enableHighAccuracy,
+ });
+ navigator.geolocation.clearWatch(id);
+ }
+ }, "Call watchPosition with wrong type for enableHighAccuracy. No exception expected.");
+
</script>
diff --git a/tests/wpt/tests/geolocation/tojson.https.window.js b/tests/wpt/tests/geolocation/tojson.https.window.js
index f8cef07a7f0..b3f8500b1a8 100644
--- a/tests/wpt/tests/geolocation/tojson.https.window.js
+++ b/tests/wpt/tests/geolocation/tojson.https.window.js
@@ -1,51 +1,37 @@
// META: script=/resources/testdriver.js
// META: script=/resources/testdriver-vendor.js
-'use strict';
+"use strict";
-function check_coords(original, json, prefix) {
- for (const key of [
- 'accuracy',
- 'latitude',
- 'longitude',
- 'altitude',
- 'altitudeAccuracy',
- 'heading',
- 'speed',
- 'coords',
- 'timestamp',
- ]) {
- assert_equals(original[key], json[key], `${prefix} ${key} entry does not match its toJSON value`);
+function check_equals(original, json) {
+ const proto = Object.getPrototypeOf(original);
+ const keys = Object.keys(proto).filter(
+ (k) => typeof original[k] !== "function",
+ );
+ for (const key of keys) {
+ assert_equals(
+ original[key],
+ json[key],
+ `${original.constructor.name} ${key} entry does not match its toJSON value`,
+ );
}
}
promise_setup(async () => {
await test_driver.set_permission({ name: "geolocation" }, "granted");
-
- if (document.readyState != 'complete') {
- await new Promise(resolve => {
- window.addEventListener('load', resolve, {once: true});
- });
- }
-}, 'Grant permission and wait for the document to be fully active.');
+});
promise_test(async (t) => {
const position = await new Promise((resolve, reject) => {
- navigator.geolocation.getCurrentPosition(
- t.step_func((position) => {
- resolve(position);
- }),
- t.step_func((error) => {
- reject(error.message);
- }),
- );
+ navigator.geolocation.getCurrentPosition(resolve, reject);
});
- assert_equals(typeof(position.toJSON), 'function');
-
const json = position.toJSON();
- assert_equals(position.timestamp, json.timestamp, 'GeolocationPosition timestamp entry does not match its toJSON value');
- check_coords(position.coords, json.coords, 'GeolocationPosition coords');
+ assert_equals(
+ position.timestamp,
+ json.timestamp,
+ "GeolocationPosition timestamp entry does not match its toJSON value",
+ );
- assert_equals(typeof(position.coords.toJSON), 'function');
- check_coords(position.coords, position.coords.toJSON(), 'GeolocationCoordinates');
-}, 'Test toJSON() in GeolocationPosition and GeolocationCoordinates.');
+ check_equals(position.coords, json.coords);
+ check_equals(position.coords, position.coords.toJSON());
+}, "Test toJSON() in GeolocationPosition and GeolocationCoordinates.");
diff --git a/tests/wpt/tests/hr-time/raf-coarsened-time.https.html b/tests/wpt/tests/hr-time/raf-coarsened-time.https.html
index e649d91c163..182c707dc3b 100644
--- a/tests/wpt/tests/hr-time/raf-coarsened-time.https.html
+++ b/tests/wpt/tests/hr-time/raf-coarsened-time.https.html
@@ -11,7 +11,7 @@
const COARSE_RESOLUTION = 0.005;
const CUSTOM_TIMELINE_DELTA = 0.002;
- const FLOATING_POINT_ERROR_EPSILON = 0.00001;
+ const FLOATING_POINT_ERROR_EPSILON = 0.01;
// Note that this test would fail if the platform introduces a jitter.
// It is recommended that platforms that implement jitter run this test
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/first-party-to-first-party-cross-partition.sub.html b/tests/wpt/tests/html/browsers/windows/post-message/first-party-to-first-party-cross-partition.sub.html
deleted file mode 100644
index f91d9403ea0..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/first-party-to-first-party-cross-partition.sub.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<title>postMessage: First-Party to First-Party, Cross-Partition</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<body>
-<script>
-// Here's the set-up for this test:
-// Step 1. (Site 1 Window) set up listener and open Site 2 Window.
-// Step 2. (Site 2 Window) send "Site 2 Window" message to Site 1 Window.
-// Step 3. (Site 1 Window) receive "Site 2 Window" message and exit.
-
-async_test(t => {
- // Step 3
- const listener = t.step_func(e => {
- if (e.data === "Site 2 Window") {
- t.done();
- }
- });
- // Step 1
- window.addEventListener("message", listener);
- const site2Window = window.open("http://{{hosts[alt][]}}:{{ports[http][0]}}/html/browsers/windows/post-message/resources/first-party-to-first-party-cross-partition-window.html", "", "noopener=false");
- t.add_cleanup(() => site2Window.close());
-}, "postMessage: First-Party to First-Party, Cross-Partition");
-</script>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/first-party-to-first-party-same-partition.html b/tests/wpt/tests/html/browsers/windows/post-message/first-party-to-first-party-same-partition.html
deleted file mode 100644
index 6fc915298bb..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/first-party-to-first-party-same-partition.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<title>postMessage: First-Party to First-Party, Same-Partition</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<body>
-<script>
-// Here's the set-up for this test:
-// Step 1. (Site 1 Window A) set up listener and open Site 1 Window B.
-// Step 2. (Site 1 Window B) send "Site 1 Window B" message to Site 1 Window A.
-// Step 3. (Site 1 Window A) receive "Site 1 Window B" message and exit.
-
-async_test(t => {
- // Step 3
- const listener = t.step_func(e => {
- if (e.data === "Site 1 Window B") {
- t.done();
- }
- });
- // Step 1
- window.addEventListener("message", listener);
- const site1WindowB = window.open("/html/browsers/windows/post-message/resources/first-party-to-first-party-same-partition-window.html", "", "noopener=false");
- t.add_cleanup(() => site1WindowB.close());
-}, "postMessage: First-Party to First-Party, Same-Partition");
-</script>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-cross-origin.sub.html b/tests/wpt/tests/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-cross-origin.sub.html
deleted file mode 100644
index de776f83818..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-cross-origin.sub.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<title>postMessage: First-Party to Third-Party, Cross-Partition, Cross-Origin</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<body>
-<script>
-// Here's the set-up for this test:
-// Step 1. (Site 1 Window) set up listener and open Site 2 Window.
-// Step 2. (Site 2 Window) open Site 3 Frame.
-// Step 3. (Site 3 Frame) send "Site 3 Frame" message to Site 1 Window.
-// Step 4. (Site 1 Window) receive "Site 1 Frame" message and exit.
-
-async_test(t => {
- // Step 4
- const listener = t.step_func(e => {
- if (e.data === "Site 3 Frame") {
- t.done();
- }
- });
- // Step 1
- window.addEventListener("message", listener);
- const site2Window = window.open("https://{{hosts[alt][]}}:{{ports[https][0]}}/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-cross-origin-window.sub.https.html", "", "noopener=false");
- t.add_cleanup(() => site2Window.close());
-}, "postMessage: First-Party to Third-Party, Cross-Partition, Cross-Origin");
-</script>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-same-origin.sub.html b/tests/wpt/tests/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-same-origin.sub.html
deleted file mode 100644
index 490b647fa4a..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/first-party-to-third-party-cross-partition-same-origin.sub.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<title>postMessage: First-Party to Third-Party, Cross-Partition, Same-Origin</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<body>
-<script>
-// Here's the set-up for this test:
-// Step 1. (Site 1 Window) set up listener and open Site 2 Window.
-// Step 2. (Site 2 Window) open Site 1 Frame.
-// Step 3. (Site 1 Frame) send "Site 1 Frame" message to Site 1 Window.
-// Step 4. (Site 1 Window) receive "Site 1 Frame" message and exit.
-
-async_test(t => {
- // Step 4
- const listener = t.step_func(e => {
- if (e.data === "Site 1 Frame") {
- t.done();
- }
- });
- // Step 1
- window.addEventListener("message", listener);
- const site2Window = window.open("http://{{hosts[alt][]}}:{{ports[http][0]}}/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-same-origin-window.sub.html", "", "noopener=false");
- t.add_cleanup(() => site2Window.close());
-}, "postMessage: First-Party to Third-Party, Cross-Partition, Same-Origin");
-</script>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-first-party-cross-partition-window.html b/tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-first-party-cross-partition-window.html
deleted file mode 100644
index cdfa9d95caa..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-first-party-cross-partition-window.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<body>
-<script>
-// Step 2 (html/browsers/windows/post-message/first-party-to-first-party-cross-partition.sub.html)
-window.opener.postMessage("Site 2 Window", "*");
-</script>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-first-party-same-partition-window.html b/tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-first-party-same-partition-window.html
deleted file mode 100644
index 4baf45d3c82..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-first-party-same-partition-window.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<body>
-<script>
-// Step 2 (html/browsers/windows/post-message/first-party-to-first-party-same-partition.html)
-window.opener.postMessage("Site 1 Window B", window.opener.origin);
-</script>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-cross-origin-iframe.https.html b/tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-cross-origin-iframe.https.html
deleted file mode 100644
index eef594831aa..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-cross-origin-iframe.https.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<body>
-<script>
-// Step 3 (html/browsers/windows/post-message/first-party-to-third-party-cross-partition-cross-origin.sub.html)
-window.top.opener.postMessage("Site 3 Frame", "*");
-</script>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-cross-origin-window.sub.https.html b/tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-cross-origin-window.sub.https.html
deleted file mode 100644
index 7edfbd93281..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-cross-origin-window.sub.https.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<body>
-<!--Step 2 (html/browsers/windows/post-message/first-party-to-third-party-cross-partition-cross-origin.sub.html)-->
-<iframe src="https://{{host}}:{{ports[https][0]}}/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-cross-origin-iframe.https.html"></iframe>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-same-origin-iframe.html b/tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-same-origin-iframe.html
deleted file mode 100644
index eb17036c8cc..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-same-origin-iframe.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<body>
-<script>
-// Step 3 (html/browsers/windows/post-message/first-party-to-third-party-cross-partition-same-origin.sub.html)
-window.top.opener.postMessage("Site 1 Frame", window.top.opener.origin);
-</script>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-same-origin-window.sub.html b/tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-same-origin-window.sub.html
deleted file mode 100644
index f99b96c4667..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-same-origin-window.sub.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<body>
-<!--Step 2 (html/browsers/windows/post-message/first-party-to-third-party-cross-partition-same-origin.sub.html)-->
-<iframe src="http://{{host}}:{{ports[http][0]}}/html/browsers/windows/post-message/resources/first-party-to-third-party-cross-partition-same-origin-iframe.html"></iframe>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-cross-origin-iframe.sub.html b/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-cross-origin-iframe.sub.html
deleted file mode 100644
index 348efb3f9c4..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-cross-origin-iframe.sub.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<body>
-<script>
-// Step 4 (html/browsers/windows/post-message/third-party-to-first-party-cross-partition-cross-origin.sub.html)
-let site2Window;
-const listener = e => {
- if (e.data === "Site 3 Window") {
- site2Window.close();
- window.top.postMessage("Site 2 Frame", "*");
- }
-};
-// Step 2 (html/browsers/windows/post-message/third-party-to-first-party-cross-partition-cross-origin.sub.html)
-window.addEventListener("message", listener);
-site2Window = window.open("https://{{hosts[alt][]}}:{{ports[https][0]}}/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-cross-origin-window.https.html", "", "noopener=false");
-</script>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-cross-origin-window.https.html b/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-cross-origin-window.https.html
deleted file mode 100644
index 00459094941..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-cross-origin-window.https.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<body>
-<script>
-// Step 3 (html/browsers/windows/post-message/third-party-to-first-party-cross-partition-cross-origin.sub.html)
-window.opener.postMessage("Site 3 Window", "*");
-</script>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-same-origin-iframe.sub.html b/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-same-origin-iframe.sub.html
deleted file mode 100644
index 405b1053d33..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-same-origin-iframe.sub.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<body>
-<script>
-// Step 4 (html/browsers/windows/post-message/third-party-to-first-party-cross-partition-same-origin.sub.html)
-let site2Window;
-const listener = e => {
- if (e.data === "Site 2 Window") {
- site2Window.close();
- window.top.postMessage("Site 2 Frame", "*");
- }
-};
-// Step 2 (html/browsers/windows/post-message/third-party-to-first-party-cross-partition-same-origin.sub.html)
-window.addEventListener("message", listener);
-site2Window = window.open("http://{{hosts[alt][]}}:{{ports[http][0]}}/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-same-origin-window.html", "", "noopener=false");
-</script>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-same-origin-window.html b/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-same-origin-window.html
deleted file mode 100644
index 0ae51e3fc17..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-same-origin-window.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<body>
-<script>
-// Step 3 (html/browsers/windows/post-message/third-party-to-first-party-cross-partition-same-origin.sub.html)
-window.opener.postMessage("Site 2 Window", window.opener.origin);
-</script>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-iframe-a.sub.html b/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-iframe-a.sub.html
deleted file mode 100644
index a11f3640e83..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-iframe-a.sub.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<body>
-<script>
-// Step 5 (html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html)
-let site3Window;
-const listener = e => {
- if (e.data === "Site 4 Frame") {
- site3Window.close();
- window.top.postMessage("Site 2 Frame", "*");
- }
-};
-// Step 2 (html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html)
-window.addEventListener("message", listener);
-site3Window = window.open("https://{{host}}:{{ports[https][0]}}/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-window.sub.https.html", "", "noopener=false");
-</script>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-iframe-b.https.html b/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-iframe-b.https.html
deleted file mode 100644
index f56e2b35d8b..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-iframe-b.https.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<body>
-<script>
-// Step 4 (html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html)
-window.top.opener.postMessage("Site 4 Frame", "*");
-</script>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-window.sub.https.html b/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-window.sub.https.html
deleted file mode 100644
index 13072875879..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-window.sub.https.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<body>
-<!--Step 3 (html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html)-->
-<iframe src="https://{{hosts[alt][]}}:{{ports[https][0]}}/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-iframe-b.https.html"></iframe>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-iframe-a.sub.https.html b/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-iframe-a.sub.https.html
deleted file mode 100644
index ec551bfdec9..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-iframe-a.sub.https.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<body>
-<script>
-// Step 5 (html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html)
-let site3Window;
-const listener = e => {
- if (e.data === "Site 2 Frame B") {
- site3Window.close();
- window.top.postMessage("Site 2 Frame A", "*");
- }
-};
-// Step 2 (html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html)
-window.addEventListener("message", listener);
-site3Window = window.open("https://{{host}}:{{ports[https][0]}}/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-window.sub.https.html", "", "noopener=false");
-</script>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-iframe-b.https.html b/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-iframe-b.https.html
deleted file mode 100644
index fc15b1f1252..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-iframe-b.https.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<body>
-<script>
-// Step 4 (html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html)
-window.top.opener.postMessage("Site 2 Frame B", window.top.opener.origin);
-</script>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-window.sub.https.html b/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-window.sub.https.html
deleted file mode 100644
index f26f709db34..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-window.sub.https.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<body>
-<!--Step 3 (html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html)-->
-<iframe src="https://{{hosts[alt][]}}:{{ports[https][0]}}/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-iframe-b.https.html"></iframe>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-iframe-a.sub.html b/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-iframe-a.sub.html
deleted file mode 100644
index 339dc9f06eb..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-iframe-a.sub.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<body>
-<script>
-// Step 5 (html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html)
-let site2WindowB;
-const listener = e => {
- if (e.data === "Site 2 Frame B") {
- site2WindowB.close();
- window.top.postMessage("Site 2 Frame A", "*");
- }
-};
-// Step 2 (html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html)
-window.addEventListener("message", listener);
-site2WindowB = window.open("http://{{host}}:{{ports[http][0]}}/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-window.sub.html", "", "noopener=false");
-</script>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-iframe-b.html b/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-iframe-b.html
deleted file mode 100644
index daaf236bfca..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-iframe-b.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<body>
-<script>
-// Step 4 (html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html)
-window.top.opener.postMessage("Site 2 Frame B", window.top.opener.origin);
-</script>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-window.sub.html b/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-window.sub.html
deleted file mode 100644
index 7cea58f235c..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-window.sub.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<body>
-<!--Step 3 (html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html)-->
-<iframe src="http://{{hosts[alt][]}}:{{ports[http][0]}}/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-iframe-b.html"></iframe>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/third-party-to-first-party-cross-partition-cross-origin.sub.html b/tests/wpt/tests/html/browsers/windows/post-message/third-party-to-first-party-cross-partition-cross-origin.sub.html
deleted file mode 100644
index 2618e966de6..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/third-party-to-first-party-cross-partition-cross-origin.sub.html
+++ /dev/null
@@ -1,29 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<title>postMessage: Third-Party to First-Party, Cross-Partition, Cross-Origin</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<body>
-<script>
-// Here's the set-up for this test:
-// Step 1. (Site 1 Window) set up listener and open Site 2 Frame.
-// Step 2. (Site 2 Frame) set up listener and open Site 3 Window.
-// Step 3. (Site 3 Window) send "Site 3 Window" message to Site 2 Frame.
-// Step 4. (Site 2 Frame) receive "Site 3 Window" message and send "Site 2 Frame" message to Site 1 Window.
-// Step 5. (Site 1 Window) receive "Site 2 Frame" message and exit.
-
-async_test(t => {
- // Step 5
- const listener = t.step_func(e => {
- if (e.data === "Site 2 Frame") {
- t.done();
- }
- });
- // Step 1
- window.addEventListener("message", listener);
- const site2Frame = document.createElement("iframe");
- site2Frame.src = "http://{{hosts[alt][]}}:{{ports[http][0]}}/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-cross-origin-iframe.sub.html";
- document.body.appendChild(site2Frame);
-}, "postMessage: Third-Party to First-Party, Cross-Partition, Cross-Origin");
-</script>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/third-party-to-first-party-cross-partition-same-origin.sub.html b/tests/wpt/tests/html/browsers/windows/post-message/third-party-to-first-party-cross-partition-same-origin.sub.html
deleted file mode 100644
index 735b4588411..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/third-party-to-first-party-cross-partition-same-origin.sub.html
+++ /dev/null
@@ -1,29 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<title>postMessage: Third-Party to First-Party, Cross-Partition, Same-Origin</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<body>
-<script>
-// Here's the set-up for this test:
-// Step 1. (Site 1 Window) set up listener and open Site 2 Frame.
-// Step 2. (Site 2 Frame) set up listener and open Site 2 Window.
-// Step 3. (Site 2 Window) send "Site 2 Window" message to Site 2 Frame.
-// Step 4. (Site 2 Frame) receive "Site 2 Window" message and send "Site 2 Frame" message to Site 1 Window.
-// Step 5. (Site 1 Window) receive "Site 2 Frame" message and exit.
-
-async_test(t => {
- // Step 5
- const listener = t.step_func(e => {
- if (e.data === "Site 2 Frame") {
- t.done();
- }
- });
- // Step 1
- window.addEventListener("message", listener);
- const site2Frame = document.createElement("iframe");
- site2Frame.src = "http://{{hosts[alt][]}}:{{ports[http][0]}}/html/browsers/windows/post-message/resources/third-party-to-first-party-cross-partition-same-origin-iframe.sub.html";
- document.body.appendChild(site2Frame);
-}, "postMessage: Third-Party to First-Party, Cross-Partition, Same-Origin");
-</script>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html b/tests/wpt/tests/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html
deleted file mode 100644
index 1083071f7d2..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-cross-origin.sub.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<title>postMessage: Third-Party to Third-Party, Cross-Partition, Cross-Origin</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<body>
-<script>
-// Here's the set-up for this test:
-// Step 1. (Site 1 Window) set up listener and open Site 2 Frame.
-// Step 2. (Site 2 Frame) set up listener and open Site 3 Window.
-// Step 3. (Site 3 Window) open Site 4 Frame.
-// Step 4. (Site 4 Frame) send "Site 4 Frame" message to Site 2 Frame.
-// Step 5. (Site 2 Frame) receive "Site 4 Frame" message and send "Site 2 Frame" message to Site 1 Window.
-// Step 6. (Site 1 Window) receive "Site 2 Frame" message and exit.
-
-async_test(t => {
- // Step 6
- const listener = t.step_func(e => {
- if (e.data === "Site 2 Frame") {
- t.done();
- }
- });
- // Step 1
- window.addEventListener("message", listener);
- const site2FrameA = document.createElement("iframe");
- site2FrameA.src = "http://{{hosts[alt][]}}:{{ports[http][0]}}/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-cross-origin-iframe-a.sub.html";
- document.body.appendChild(site2FrameA);
-}, "postMessage: Third-Party to Third-Party, Cross-Partition, Cross-Origin");
-</script>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html b/tests/wpt/tests/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html
deleted file mode 100644
index 9caf6c11e48..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/third-party-to-third-party-cross-partition-same-origin.sub.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<title>postMessage: Third-Party to Third-Party, Cross-Partition, Same-Origin</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<body>
-<script>
-// Here's the set-up for this test:
-// Step 1. (Site 1 Window) set up listener and open Site 2 Frame A.
-// Step 2. (Site 2 Frame A) set up listener and open Site 3 Window.
-// Step 3. (Site 3 Window) open Site 2 Frame B.
-// Step 4. (Site 2 Frame B) send "Site 2 Frame B" message to Site 2 Frame A.
-// Step 5. (Site 2 Frame A) receive "Site 2 Frame B" message and send "Site 2 Frame A" message to Site 1 Window.
-// Step 6. (Site 1 Window) receive "Site 2 Frame A" message and exit.
-
-async_test(t => {
- // Step 6
- const listener = t.step_func(e => {
- if (e.data === "Site 2 Frame A") {
- t.done();
- }
- });
- // Step 1
- window.addEventListener("message", listener);
- const site2FrameA = document.createElement("iframe");
- site2FrameA.src = "https://{{hosts[alt][]}}:{{ports[https][0]}}/html/browsers/windows/post-message/resources/third-party-to-third-party-cross-partition-same-origin-iframe-a.sub.https.html";
- document.body.appendChild(site2FrameA);
-}, "postMessage: Third-Party to Third-Party, Cross-Partition, Same-Origin");
-</script>
-</body>
diff --git a/tests/wpt/tests/html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html b/tests/wpt/tests/html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html
deleted file mode 100644
index c90a0552684..00000000000
--- a/tests/wpt/tests/html/browsers/windows/post-message/third-party-to-third-party-same-partition.sub.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<title>postMessage: Third-Party to Third-Party, Same-Partition</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<body>
-<script>
-// Here's the set-up for this test:
-// Step 1. (Site 1 Window A) set up listener and open Site 2 Frame A.
-// Step 2. (Site 2 Frame A) set up listener and open Site 1 Window B.
-// Step 3. (Site 1 Window B) open Site 2 Frame B.
-// Step 4. (Site 2 Frame B) send "Site 2 Frame B" message to Site 2 Frame A.
-// Step 5. (Site 2 Frame A) receive "Site 2 Frame B" message and send "Site 2 Frame A" message to Site 1 Window A.
-// Step 6. (Site 1 Window A) receive "Site 2 Frame A" message and exit.
-
-async_test(t => {
- // Step 6
- const listener = t.step_func(e => {
- if (e.data === "Site 2 Frame A") {
- t.done();
- }
- });
- // Step 1
- window.addEventListener("message", listener);
- const site2FrameA = document.createElement("iframe");
- site2FrameA.src = "http://{{hosts[alt][]}}:{{ports[http][0]}}/html/browsers/windows/post-message/resources/third-party-to-third-party-same-partition-iframe-a.sub.html";
- document.body.appendChild(site2FrameA);
-}, "postMessage: Third-Party to Third-Party, Same-Partition");
-</script>
-</body>
diff --git a/tests/wpt/tests/html/canvas/element/text/2d.text.measure.caret-position-edge-cases.tentative.html b/tests/wpt/tests/html/canvas/element/text/2d.text.measure.caret-position-edge-cases.tentative.html
new file mode 100644
index 00000000000..b1d4b873973
--- /dev/null
+++ b/tests/wpt/tests/html/canvas/element/text/2d.text.measure.caret-position-edge-cases.tentative.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: 2d.text.measure.caret-position-edge-cases.tentative</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<style>
+@font-face {
+ font-family: CanvasTest;
+ src: url("/fonts/CanvasTest.ttf");
+}
+</style>
+<body class="show_output">
+
+<h1>2d.text.measure.caret-position-edge-cases.tentative</h1>
+<p class="desc">Test the edge cases for caretPositionFromPoint, where the point is at the edge of glyph and at the midpoint.</p>
+
+
+<span style="font-family: CanvasTest; position: absolute; visibility: hidden">A</span>
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+promise_test(async t => {
+
+ var canvas = document.getElementById('c');
+ var ctx = canvas.getContext('2d');
+
+ await document.fonts.ready;
+ ctx.font = '50px CanvasTest';
+ ctx.direction = 'ltr';
+ ctx.align = 'left'
+ ctx.baseline = 'alphabetic'
+ tm = ctx.measureText('A');
+ const a_width = tm.width;
+ tm = ctx.measureText('B');
+ const b_width = tm.width;
+ tm = ctx.measureText('C');
+ const c_width = tm.width;
+ const epsilon = 1.0e-4;
+
+ tm = ctx.measureText('ABC');
+ _assert(tm.caretPositionFromPoint(0) == 0, "tm.caretPositionFromPoint(0) == 0");
+ _assert(tm.caretPositionFromPoint(a_width / 2) == 0, "tm.caretPositionFromPoint(a_width / 2) == 0");
+ _assert(tm.caretPositionFromPoint(a_width / 2 + 1) == 1, "tm.caretPositionFromPoint(a_width / 2 + 1) == 1");
+ _assert(tm.caretPositionFromPoint(a_width) == 1, "tm.caretPositionFromPoint(a_width) == 1");
+ _assert(tm.caretPositionFromPoint(a_width + b_width / 2) == 1, "tm.caretPositionFromPoint(a_width + b_width / 2) == 1");
+ _assert(tm.caretPositionFromPoint(a_width + b_width / 2 + 1) == 2, "tm.caretPositionFromPoint(a_width + b_width / 2 + 1) == 2");
+ _assert(tm.caretPositionFromPoint(a_width + b_width) == 2, "tm.caretPositionFromPoint(a_width + b_width) == 2");
+ _assert(tm.caretPositionFromPoint(a_width + b_width + c_width / 2) == 2, "tm.caretPositionFromPoint(a_width + b_width + c_width / 2) == 2");
+ _assert(tm.caretPositionFromPoint(a_width + b_width + c_width / 2 + 1) == 3, "tm.caretPositionFromPoint(a_width + b_width + c_width / 2 + 1) == 3");
+ _assert(tm.caretPositionFromPoint(a_width + b_width + c_width) == 3, "tm.caretPositionFromPoint(a_width + b_width + c_width) == 3");
+
+}, "Test the edge cases for caretPositionFromPoint, where the point is at the edge of glyph and at the midpoint.");
+</script>
+
diff --git a/tests/wpt/tests/html/canvas/element/text/2d.text.measure.caret-position-edges.tentative.html b/tests/wpt/tests/html/canvas/element/text/2d.text.measure.caret-position-edges.tentative.html
new file mode 100644
index 00000000000..3a772bfd6cd
--- /dev/null
+++ b/tests/wpt/tests/html/canvas/element/text/2d.text.measure.caret-position-edges.tentative.html
@@ -0,0 +1,714 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: 2d.text.measure.caret-position-edges.tentative</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+
+<h1>2d.text.measure.caret-position-edges.tentative</h1>
+
+<script>
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('left' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('ltr' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('left' == 'start' || 'left' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('left' == 'start' || 'left' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align left.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('left' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('rtl' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('left' == 'start' || 'left' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('left' == 'start' || 'left' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align left.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('center' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('ltr' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('center' == 'start' || 'center' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('center' == 'start' || 'center' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align center.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('center' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('rtl' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('center' == 'start' || 'center' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('center' == 'start' || 'center' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align center.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('right' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('ltr' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('right' == 'start' || 'right' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('right' == 'start' || 'right' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align right.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('right' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('rtl' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('right' == 'start' || 'right' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('right' == 'start' || 'right' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align right.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('start' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('ltr' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('start' == 'start' || 'start' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('start' == 'start' || 'start' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'start';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align start.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('start' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('rtl' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('start' == 'start' || 'start' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('start' == 'start' || 'start' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'start';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align start.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('end' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('ltr' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('end' == 'start' || 'end' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('end' == 'start' || 'end' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'end';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align end.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('end' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('rtl' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('end' == 'start' || 'end' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('end' == 'start' || 'end' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'end';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align end.");
+
+</script>
+</div>
diff --git a/tests/wpt/tests/html/canvas/element/text/2d.text.measure.caret-position.tentative.html b/tests/wpt/tests/html/canvas/element/text/2d.text.measure.caret-position.tentative.html
new file mode 100644
index 00000000000..53c91264d7c
--- /dev/null
+++ b/tests/wpt/tests/html/canvas/element/text/2d.text.measure.caret-position.tentative.html
@@ -0,0 +1,4074 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: 2d.text.measure.caret-position.tentative</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+
+<h1>2d.text.measure.caret-position.tentative</h1>
+
+<script>
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('left' == 'center') {
+ offset -= width / 2;
+ } else if ('left' == 'right' ||
+ ('ltr' == 'ltr' && 'left' == 'end') ||
+ ('ltr' == 'rtl' && 'left' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('left' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('left' == 'right' ||
+ ('ltr' == 'ltr' && 'left' == 'end') ||
+ ('ltr' == 'rtl' && 'left' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 0px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('left' == 'center') {
+ offset -= width / 2;
+ } else if ('left' == 'right' ||
+ ('rtl' == 'ltr' && 'left' == 'end') ||
+ ('rtl' == 'rtl' && 'left' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('left' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('left' == 'right' ||
+ ('rtl' == 'ltr' && 'left' == 'end') ||
+ ('rtl' == 'rtl' && 'left' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 0px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('center' == 'center') {
+ offset -= width / 2;
+ } else if ('center' == 'right' ||
+ ('ltr' == 'ltr' && 'center' == 'end') ||
+ ('ltr' == 'rtl' && 'center' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('center' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('center' == 'right' ||
+ ('ltr' == 'ltr' && 'center' == 'end') ||
+ ('ltr' == 'rtl' && 'center' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 0px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('center' == 'center') {
+ offset -= width / 2;
+ } else if ('center' == 'right' ||
+ ('rtl' == 'ltr' && 'center' == 'end') ||
+ ('rtl' == 'rtl' && 'center' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('center' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('center' == 'right' ||
+ ('rtl' == 'ltr' && 'center' == 'end') ||
+ ('rtl' == 'rtl' && 'center' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 0px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('right' == 'center') {
+ offset -= width / 2;
+ } else if ('right' == 'right' ||
+ ('ltr' == 'ltr' && 'right' == 'end') ||
+ ('ltr' == 'rtl' && 'right' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('right' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('right' == 'right' ||
+ ('ltr' == 'ltr' && 'right' == 'end') ||
+ ('ltr' == 'rtl' && 'right' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 0px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('right' == 'center') {
+ offset -= width / 2;
+ } else if ('right' == 'right' ||
+ ('rtl' == 'ltr' && 'right' == 'end') ||
+ ('rtl' == 'rtl' && 'right' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('right' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('right' == 'right' ||
+ ('rtl' == 'ltr' && 'right' == 'end') ||
+ ('rtl' == 'rtl' && 'right' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 0px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('start' == 'center') {
+ offset -= width / 2;
+ } else if ('start' == 'right' ||
+ ('ltr' == 'ltr' && 'start' == 'end') ||
+ ('ltr' == 'rtl' && 'start' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('start' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('start' == 'right' ||
+ ('ltr' == 'ltr' && 'start' == 'end') ||
+ ('ltr' == 'rtl' && 'start' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'start';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 0px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('start' == 'center') {
+ offset -= width / 2;
+ } else if ('start' == 'right' ||
+ ('rtl' == 'ltr' && 'start' == 'end') ||
+ ('rtl' == 'rtl' && 'start' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('start' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('start' == 'right' ||
+ ('rtl' == 'ltr' && 'start' == 'end') ||
+ ('rtl' == 'rtl' && 'start' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'start';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 0px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('end' == 'center') {
+ offset -= width / 2;
+ } else if ('end' == 'right' ||
+ ('ltr' == 'ltr' && 'end' == 'end') ||
+ ('ltr' == 'rtl' && 'end' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('end' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('end' == 'right' ||
+ ('ltr' == 'ltr' && 'end' == 'end') ||
+ ('ltr' == 'rtl' && 'end' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'end';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 0px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('end' == 'center') {
+ offset -= width / 2;
+ } else if ('end' == 'right' ||
+ ('rtl' == 'ltr' && 'end' == 'end') ||
+ ('rtl' == 'rtl' && 'end' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('end' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('end' == 'right' ||
+ ('rtl' == 'ltr' && 'end' == 'end') ||
+ ('rtl' == 'rtl' && 'end' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'end';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 0px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('left' == 'center') {
+ offset -= width / 2;
+ } else if ('left' == 'right' ||
+ ('ltr' == 'ltr' && 'left' == 'end') ||
+ ('ltr' == 'rtl' && 'left' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('left' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('left' == 'right' ||
+ ('ltr' == 'ltr' && 'left' == 'end') ||
+ ('ltr' == 'rtl' && 'left' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 10px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('left' == 'center') {
+ offset -= width / 2;
+ } else if ('left' == 'right' ||
+ ('rtl' == 'ltr' && 'left' == 'end') ||
+ ('rtl' == 'rtl' && 'left' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('left' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('left' == 'right' ||
+ ('rtl' == 'ltr' && 'left' == 'end') ||
+ ('rtl' == 'rtl' && 'left' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 10px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('center' == 'center') {
+ offset -= width / 2;
+ } else if ('center' == 'right' ||
+ ('ltr' == 'ltr' && 'center' == 'end') ||
+ ('ltr' == 'rtl' && 'center' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('center' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('center' == 'right' ||
+ ('ltr' == 'ltr' && 'center' == 'end') ||
+ ('ltr' == 'rtl' && 'center' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 10px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('center' == 'center') {
+ offset -= width / 2;
+ } else if ('center' == 'right' ||
+ ('rtl' == 'ltr' && 'center' == 'end') ||
+ ('rtl' == 'rtl' && 'center' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('center' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('center' == 'right' ||
+ ('rtl' == 'ltr' && 'center' == 'end') ||
+ ('rtl' == 'rtl' && 'center' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 10px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('right' == 'center') {
+ offset -= width / 2;
+ } else if ('right' == 'right' ||
+ ('ltr' == 'ltr' && 'right' == 'end') ||
+ ('ltr' == 'rtl' && 'right' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('right' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('right' == 'right' ||
+ ('ltr' == 'ltr' && 'right' == 'end') ||
+ ('ltr' == 'rtl' && 'right' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 10px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('right' == 'center') {
+ offset -= width / 2;
+ } else if ('right' == 'right' ||
+ ('rtl' == 'ltr' && 'right' == 'end') ||
+ ('rtl' == 'rtl' && 'right' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('right' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('right' == 'right' ||
+ ('rtl' == 'ltr' && 'right' == 'end') ||
+ ('rtl' == 'rtl' && 'right' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 10px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('start' == 'center') {
+ offset -= width / 2;
+ } else if ('start' == 'right' ||
+ ('ltr' == 'ltr' && 'start' == 'end') ||
+ ('ltr' == 'rtl' && 'start' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('start' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('start' == 'right' ||
+ ('ltr' == 'ltr' && 'start' == 'end') ||
+ ('ltr' == 'rtl' && 'start' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'start';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 10px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('start' == 'center') {
+ offset -= width / 2;
+ } else if ('start' == 'right' ||
+ ('rtl' == 'ltr' && 'start' == 'end') ||
+ ('rtl' == 'rtl' && 'start' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('start' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('start' == 'right' ||
+ ('rtl' == 'ltr' && 'start' == 'end') ||
+ ('rtl' == 'rtl' && 'start' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'start';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 10px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('end' == 'center') {
+ offset -= width / 2;
+ } else if ('end' == 'right' ||
+ ('ltr' == 'ltr' && 'end' == 'end') ||
+ ('ltr' == 'rtl' && 'end' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('end' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('end' == 'right' ||
+ ('ltr' == 'ltr' && 'end' == 'end') ||
+ ('ltr' == 'rtl' && 'end' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'end';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 10px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('end' == 'center') {
+ offset -= width / 2;
+ } else if ('end' == 'right' ||
+ ('rtl' == 'ltr' && 'end' == 'end') ||
+ ('rtl' == 'rtl' && 'end' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('end' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('end' == 'right' ||
+ ('rtl' == 'ltr' && 'end' == 'end') ||
+ ('rtl' == 'rtl' && 'end' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'end';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 10px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('left' == 'center') {
+ offset -= width / 2;
+ } else if ('left' == 'right' ||
+ ('ltr' == 'ltr' && 'left' == 'end') ||
+ ('ltr' == 'rtl' && 'left' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('left' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('left' == 'right' ||
+ ('ltr' == 'ltr' && 'left' == 'end') ||
+ ('ltr' == 'rtl' && 'left' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 0px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('left' == 'center') {
+ offset -= width / 2;
+ } else if ('left' == 'right' ||
+ ('rtl' == 'ltr' && 'left' == 'end') ||
+ ('rtl' == 'rtl' && 'left' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('left' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('left' == 'right' ||
+ ('rtl' == 'ltr' && 'left' == 'end') ||
+ ('rtl' == 'rtl' && 'left' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 0px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('center' == 'center') {
+ offset -= width / 2;
+ } else if ('center' == 'right' ||
+ ('ltr' == 'ltr' && 'center' == 'end') ||
+ ('ltr' == 'rtl' && 'center' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('center' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('center' == 'right' ||
+ ('ltr' == 'ltr' && 'center' == 'end') ||
+ ('ltr' == 'rtl' && 'center' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 0px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('center' == 'center') {
+ offset -= width / 2;
+ } else if ('center' == 'right' ||
+ ('rtl' == 'ltr' && 'center' == 'end') ||
+ ('rtl' == 'rtl' && 'center' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('center' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('center' == 'right' ||
+ ('rtl' == 'ltr' && 'center' == 'end') ||
+ ('rtl' == 'rtl' && 'center' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 0px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('right' == 'center') {
+ offset -= width / 2;
+ } else if ('right' == 'right' ||
+ ('ltr' == 'ltr' && 'right' == 'end') ||
+ ('ltr' == 'rtl' && 'right' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('right' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('right' == 'right' ||
+ ('ltr' == 'ltr' && 'right' == 'end') ||
+ ('ltr' == 'rtl' && 'right' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 0px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('right' == 'center') {
+ offset -= width / 2;
+ } else if ('right' == 'right' ||
+ ('rtl' == 'ltr' && 'right' == 'end') ||
+ ('rtl' == 'rtl' && 'right' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('right' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('right' == 'right' ||
+ ('rtl' == 'ltr' && 'right' == 'end') ||
+ ('rtl' == 'rtl' && 'right' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 0px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('start' == 'center') {
+ offset -= width / 2;
+ } else if ('start' == 'right' ||
+ ('ltr' == 'ltr' && 'start' == 'end') ||
+ ('ltr' == 'rtl' && 'start' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('start' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('start' == 'right' ||
+ ('ltr' == 'ltr' && 'start' == 'end') ||
+ ('ltr' == 'rtl' && 'start' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'start';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 0px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('start' == 'center') {
+ offset -= width / 2;
+ } else if ('start' == 'right' ||
+ ('rtl' == 'ltr' && 'start' == 'end') ||
+ ('rtl' == 'rtl' && 'start' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('start' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('start' == 'right' ||
+ ('rtl' == 'ltr' && 'start' == 'end') ||
+ ('rtl' == 'rtl' && 'start' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'start';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 0px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('end' == 'center') {
+ offset -= width / 2;
+ } else if ('end' == 'right' ||
+ ('ltr' == 'ltr' && 'end' == 'end') ||
+ ('ltr' == 'rtl' && 'end' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('end' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('end' == 'right' ||
+ ('ltr' == 'ltr' && 'end' == 'end') ||
+ ('ltr' == 'rtl' && 'end' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'end';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 0px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('end' == 'center') {
+ offset -= width / 2;
+ } else if ('end' == 'right' ||
+ ('rtl' == 'ltr' && 'end' == 'end') ||
+ ('rtl' == 'rtl' && 'end' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('end' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('end' == 'right' ||
+ ('rtl' == 'ltr' && 'end' == 'end') ||
+ ('rtl' == 'rtl' && 'end' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'end';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 0px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('left' == 'center') {
+ offset -= width / 2;
+ } else if ('left' == 'right' ||
+ ('ltr' == 'ltr' && 'left' == 'end') ||
+ ('ltr' == 'rtl' && 'left' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('left' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('left' == 'right' ||
+ ('ltr' == 'ltr' && 'left' == 'end') ||
+ ('ltr' == 'rtl' && 'left' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 10px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('left' == 'center') {
+ offset -= width / 2;
+ } else if ('left' == 'right' ||
+ ('rtl' == 'ltr' && 'left' == 'end') ||
+ ('rtl' == 'rtl' && 'left' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('left' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('left' == 'right' ||
+ ('rtl' == 'ltr' && 'left' == 'end') ||
+ ('rtl' == 'rtl' && 'left' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 10px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('center' == 'center') {
+ offset -= width / 2;
+ } else if ('center' == 'right' ||
+ ('ltr' == 'ltr' && 'center' == 'end') ||
+ ('ltr' == 'rtl' && 'center' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('center' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('center' == 'right' ||
+ ('ltr' == 'ltr' && 'center' == 'end') ||
+ ('ltr' == 'rtl' && 'center' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 10px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('center' == 'center') {
+ offset -= width / 2;
+ } else if ('center' == 'right' ||
+ ('rtl' == 'ltr' && 'center' == 'end') ||
+ ('rtl' == 'rtl' && 'center' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('center' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('center' == 'right' ||
+ ('rtl' == 'ltr' && 'center' == 'end') ||
+ ('rtl' == 'rtl' && 'center' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 10px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('right' == 'center') {
+ offset -= width / 2;
+ } else if ('right' == 'right' ||
+ ('ltr' == 'ltr' && 'right' == 'end') ||
+ ('ltr' == 'rtl' && 'right' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('right' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('right' == 'right' ||
+ ('ltr' == 'ltr' && 'right' == 'end') ||
+ ('ltr' == 'rtl' && 'right' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 10px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('right' == 'center') {
+ offset -= width / 2;
+ } else if ('right' == 'right' ||
+ ('rtl' == 'ltr' && 'right' == 'end') ||
+ ('rtl' == 'rtl' && 'right' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('right' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('right' == 'right' ||
+ ('rtl' == 'ltr' && 'right' == 'end') ||
+ ('rtl' == 'rtl' && 'right' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 10px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('start' == 'center') {
+ offset -= width / 2;
+ } else if ('start' == 'right' ||
+ ('ltr' == 'ltr' && 'start' == 'end') ||
+ ('ltr' == 'rtl' && 'start' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('start' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('start' == 'right' ||
+ ('ltr' == 'ltr' && 'start' == 'end') ||
+ ('ltr' == 'rtl' && 'start' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'start';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 10px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('start' == 'center') {
+ offset -= width / 2;
+ } else if ('start' == 'right' ||
+ ('rtl' == 'ltr' && 'start' == 'end') ||
+ ('rtl' == 'rtl' && 'start' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('start' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('start' == 'right' ||
+ ('rtl' == 'ltr' && 'start' == 'end') ||
+ ('rtl' == 'rtl' && 'start' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'start';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 10px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('end' == 'center') {
+ offset -= width / 2;
+ } else if ('end' == 'right' ||
+ ('ltr' == 'ltr' && 'end' == 'end') ||
+ ('ltr' == 'rtl' && 'end' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('end' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('end' == 'right' ||
+ ('ltr' == 'ltr' && 'end' == 'end') ||
+ ('ltr' == 'rtl' && 'end' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'end';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 10px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('end' == 'center') {
+ offset -= width / 2;
+ } else if ('end' == 'right' ||
+ ('rtl' == 'ltr' && 'end' == 'end') ||
+ ('rtl' == 'rtl' && 'end' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('end' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('end' == 'right' ||
+ ('rtl' == 'ltr' && 'end' == 'end') ||
+ ('rtl' == 'rtl' && 'end' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'end';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 10px letter spacing and directional-override.");
+
+</script>
+</div>
diff --git a/tests/wpt/tests/html/canvas/element/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html b/tests/wpt/tests/html/canvas/element/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html
new file mode 100644
index 00000000000..8ce7ed79482
--- /dev/null
+++ b/tests/wpt/tests/html/canvas/element/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: 2d.text.measure.getActualBoundingBox-exceptions.tentative</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>2d.text.measure.getActualBoundingBox-exceptions.tentative</h1>
+<p class="desc">Check that TextMetrics::getActualBoundingBox() throws when using invalid indexes.</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Check that TextMetrics::getActualBoundingBox() throws when using invalid indexes.");
+_addTest(function(canvas, ctx) {
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (const text of kTexts) {
+ const tm = ctx.measureText(text);
+ // Handled by the IDL binding.
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(-1, 0) );
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(0, -1) );
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(-1, -1) );
+ // Thrown in TextMetrics.
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(text.length, 0) );
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(0, text.length + 1) );
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(text.length, text.length + 1) );
+
+});
+</script>
+
diff --git a/tests/wpt/tests/html/canvas/element/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html b/tests/wpt/tests/html/canvas/element/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html
new file mode 100644
index 00000000000..510bce84f28
--- /dev/null
+++ b/tests/wpt/tests/html/canvas/element/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html
@@ -0,0 +1,278 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: 2d.text.measure.getActualBoundingBox-full-text.tentative</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+
+<h1>2d.text.measure.getActualBoundingBox-full-text.tentative</h1>
+
+<script>
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and no-directional-override");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and no-directional-override");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and directional-override");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and directional-override");
+
+</script>
+</div>
diff --git a/tests/wpt/tests/html/canvas/element/text/2d.text.measure.getActualBoundingBox.tentative.html b/tests/wpt/tests/html/canvas/element/text/2d.text.measure.getActualBoundingBox.tentative.html
new file mode 100644
index 00000000000..6f3b66a355b
--- /dev/null
+++ b/tests/wpt/tests/html/canvas/element/text/2d.text.measure.getActualBoundingBox.tentative.html
@@ -0,0 +1,502 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: 2d.text.measure.getActualBoundingBox.tentative</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+
+<h1>2d.text.measure.getActualBoundingBox.tentative</h1>
+
+<style>
+ @font-face {
+ font-family: CanvasTest;
+ src: url("/fonts/CanvasTest.ttf");
+ }
+</style>
+<span style="font-family: CanvasTest;
+ position: absolute; visibility: hidden">A</span>
+<script>
+
+promise_test(async t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ await document.fonts.ready;
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '0px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align left , and 0px letter spacing.");
+
+promise_test(async t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ await document.fonts.ready;
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '0px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align center , and 0px letter spacing.");
+
+promise_test(async t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ await document.fonts.ready;
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '0px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align right , and 0px letter spacing.");
+
+promise_test(async t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ await document.fonts.ready;
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '10px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align left , and 10px letter spacing.");
+
+promise_test(async t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ await document.fonts.ready;
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '10px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align center , and 10px letter spacing.");
+
+promise_test(async t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ await document.fonts.ready;
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '10px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align right , and 10px letter spacing.");
+
+</script>
+</div>
diff --git a/tests/wpt/tests/html/canvas/element/text/2d.text.measure.selection-rects-baselines.tentative.html b/tests/wpt/tests/html/canvas/element/text/2d.text.measure.selection-rects-baselines.tentative.html
index 6c634b93190..4c6f8288983 100644
--- a/tests/wpt/tests/html/canvas/element/text/2d.text.measure.selection-rects-baselines.tentative.html
+++ b/tests/wpt/tests/html/canvas/element/text/2d.text.measure.selection-rects-baselines.tentative.html
@@ -33,7 +33,8 @@ _addTest(function(canvas, ctx) {
const kTexts = [
'UNAVAILABLE',
'🏁🎶🏁',
- ')(あ)('
+ ')(あ)(',
+ '-abcd_'
]
for (const text of kTexts) {
diff --git a/tests/wpt/tests/html/canvas/element/text/2d.text.measure.selection-rects-exceptions.tentative.html b/tests/wpt/tests/html/canvas/element/text/2d.text.measure.selection-rects-exceptions.tentative.html
index dd213d3e83b..565834280e5 100644
--- a/tests/wpt/tests/html/canvas/element/text/2d.text.measure.selection-rects-exceptions.tentative.html
+++ b/tests/wpt/tests/html/canvas/element/text/2d.text.measure.selection-rects-exceptions.tentative.html
@@ -22,7 +22,8 @@ _addTest(function(canvas, ctx) {
const kTexts = [
'UNAVAILABLE',
'🏁🎶🏁',
- ')(あ)('
+ ')(あ)(',
+ '-abcd_'
]
for (const text of kTexts) {
diff --git a/tests/wpt/tests/html/canvas/element/text/2d.text.measure.selection-rects.tentative.html b/tests/wpt/tests/html/canvas/element/text/2d.text.measure.selection-rects.tentative.html
index e724586f083..9de40eb61f2 100644
--- a/tests/wpt/tests/html/canvas/element/text/2d.text.measure.selection-rects.tentative.html
+++ b/tests/wpt/tests/html/canvas/element/text/2d.text.measure.selection-rects.tentative.html
@@ -5,24 +5,134 @@
<script src="/resources/testharnessreport.js"></script>
<script src="/html/canvas/resources/canvas-tests.js"></script>
<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
-<body class="show_output">
<h1>2d.text.measure.selection-rects.tentative</h1>
-<p class="desc">Check that TextMetrics::getSelectionRects() matches its DOM equivalent.</p>
+<script>
-<p class="output">Actual output:</p>
-<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
-<ul id="d"></ul>
-<script>
-var t = async_test("Check that TextMetrics::getSelectionRects() matches its DOM equivalent.");
-_addTest(function(canvas, ctx) {
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'left';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 0px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'left';
+ el.style.letterSpacing = '0px';
document.body.appendChild(el);
let range = document.createRange();
@@ -30,6 +140,10 @@ _addTest(function(canvas, ctx) {
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
@@ -42,8 +156,21 @@ _addTest(function(canvas, ctx) {
document.body.removeChild(el);
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
for(let i = 0 ; i < sel_rects.length ; ++i) {
- sel_rects[i].x -= parent.x;
+ sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
@@ -62,14 +189,18 @@ _addTest(function(canvas, ctx) {
}
ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '0px';
const kTexts = [
'UNAVAILABLE',
'🏁🎶🏁',
- ')(あ)('
+ ')(あ)(',
+ '-abcd_'
]
- for (const text of kTexts) {
+ for (text of kTexts) {
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
@@ -101,7 +232,2615 @@ _addTest(function(canvas, ctx) {
placeAndSelectTextInDOM(text, 1, 0)
);
}
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 0px letter spacing, and no-directional-override.");
-});
-</script>
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'center';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 0px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'center';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 0px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'right';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 0px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'right';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 0px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'left';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 10px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'left';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 10px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'center';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 10px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'center';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 10px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'right';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 10px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'right';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 10px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'left';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 0px letter spacing, and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'left';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 0px letter spacing, and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'center';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 0px letter spacing, and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'center';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 0px letter spacing, and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'right';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 0px letter spacing, and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'right';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 0px letter spacing, and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'left';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 10px letter spacing, and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'left';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 10px letter spacing, and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'center';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 10px letter spacing, and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'center';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 10px letter spacing, and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'right';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 10px letter spacing, and directional-override.");
+
+test(t => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'right';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 10px letter spacing, and directional-override.");
+
+</script>
+</div>
diff --git a/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.html b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.html
new file mode 100644
index 00000000000..21139126fa9
--- /dev/null
+++ b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>OffscreenCanvas test: 2d.text.measure.caret-position-edge-cases.tentative</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+
+<h1>2d.text.measure.caret-position-edge-cases.tentative</h1>
+<p class="desc">Test the edge cases for caretPositionFromPoint, where the point is at the edge of glyph and at the midpoint.</p>
+
+
+<script>
+promise_test(async t => {
+
+ var canvas = new OffscreenCanvas(100, 50);
+ var ctx = canvas.getContext('2d');
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ document.fonts.add(f);
+ await document.fonts.ready;
+ ctx.font = '50px CanvasTest';
+ ctx.direction = 'ltr';
+ ctx.align = 'left'
+ ctx.baseline = 'alphabetic'
+ tm = ctx.measureText('A');
+ const a_width = tm.width;
+ tm = ctx.measureText('B');
+ const b_width = tm.width;
+ tm = ctx.measureText('C');
+ const c_width = tm.width;
+ const epsilon = 1.0e-4;
+
+ tm = ctx.measureText('ABC');
+ _assert(tm.caretPositionFromPoint(0) == 0, "tm.caretPositionFromPoint(0) == 0");
+ _assert(tm.caretPositionFromPoint(a_width / 2) == 0, "tm.caretPositionFromPoint(a_width / 2) == 0");
+ _assert(tm.caretPositionFromPoint(a_width / 2 + 1) == 1, "tm.caretPositionFromPoint(a_width / 2 + 1) == 1");
+ _assert(tm.caretPositionFromPoint(a_width) == 1, "tm.caretPositionFromPoint(a_width) == 1");
+ _assert(tm.caretPositionFromPoint(a_width + b_width / 2) == 1, "tm.caretPositionFromPoint(a_width + b_width / 2) == 1");
+ _assert(tm.caretPositionFromPoint(a_width + b_width / 2 + 1) == 2, "tm.caretPositionFromPoint(a_width + b_width / 2 + 1) == 2");
+ _assert(tm.caretPositionFromPoint(a_width + b_width) == 2, "tm.caretPositionFromPoint(a_width + b_width) == 2");
+ _assert(tm.caretPositionFromPoint(a_width + b_width + c_width / 2) == 2, "tm.caretPositionFromPoint(a_width + b_width + c_width / 2) == 2");
+ _assert(tm.caretPositionFromPoint(a_width + b_width + c_width / 2 + 1) == 3, "tm.caretPositionFromPoint(a_width + b_width + c_width / 2 + 1) == 3");
+ _assert(tm.caretPositionFromPoint(a_width + b_width + c_width) == 3, "tm.caretPositionFromPoint(a_width + b_width + c_width) == 3");
+
+}, "Test the edge cases for caretPositionFromPoint, where the point is at the edge of glyph and at the midpoint.");
+</script>
diff --git a/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.worker.js b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.worker.js
new file mode 100644
index 00000000000..83539222836
--- /dev/null
+++ b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.caret-position-edge-cases.tentative.worker.js
@@ -0,0 +1,41 @@
+// DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
+// OffscreenCanvas test in a worker:2d.text.measure.caret-position-edge-cases.tentative
+// Description:Test the edge cases for caretPositionFromPoint, where the point is at the edge of glyph and at the midpoint.
+// Note:
+
+importScripts("/resources/testharness.js");
+importScripts("/html/canvas/resources/canvas-tests.js");
+
+promise_test(async t => {
+ var canvas = new OffscreenCanvas(100, 50);
+ var ctx = canvas.getContext('2d');
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ self.fonts.add(f);
+ await self.fonts.ready;
+ ctx.font = '50px CanvasTest';
+ ctx.direction = 'ltr';
+ ctx.align = 'left'
+ ctx.baseline = 'alphabetic'
+ tm = ctx.measureText('A');
+ const a_width = tm.width;
+ tm = ctx.measureText('B');
+ const b_width = tm.width;
+ tm = ctx.measureText('C');
+ const c_width = tm.width;
+ const epsilon = 1.0e-4;
+
+ tm = ctx.measureText('ABC');
+ _assert(tm.caretPositionFromPoint(0) == 0, "tm.caretPositionFromPoint(0) == 0");
+ _assert(tm.caretPositionFromPoint(a_width / 2) == 0, "tm.caretPositionFromPoint(a_width / 2) == 0");
+ _assert(tm.caretPositionFromPoint(a_width / 2 + 1) == 1, "tm.caretPositionFromPoint(a_width / 2 + 1) == 1");
+ _assert(tm.caretPositionFromPoint(a_width) == 1, "tm.caretPositionFromPoint(a_width) == 1");
+ _assert(tm.caretPositionFromPoint(a_width + b_width / 2) == 1, "tm.caretPositionFromPoint(a_width + b_width / 2) == 1");
+ _assert(tm.caretPositionFromPoint(a_width + b_width / 2 + 1) == 2, "tm.caretPositionFromPoint(a_width + b_width / 2 + 1) == 2");
+ _assert(tm.caretPositionFromPoint(a_width + b_width) == 2, "tm.caretPositionFromPoint(a_width + b_width) == 2");
+ _assert(tm.caretPositionFromPoint(a_width + b_width + c_width / 2) == 2, "tm.caretPositionFromPoint(a_width + b_width + c_width / 2) == 2");
+ _assert(tm.caretPositionFromPoint(a_width + b_width + c_width / 2 + 1) == 3, "tm.caretPositionFromPoint(a_width + b_width + c_width / 2 + 1) == 3");
+ _assert(tm.caretPositionFromPoint(a_width + b_width + c_width) == 3, "tm.caretPositionFromPoint(a_width + b_width + c_width) == 3");
+}, "Test the edge cases for caretPositionFromPoint, where the point is at the edge of glyph and at the midpoint.");
+done();
diff --git a/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.html b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.html
new file mode 100644
index 00000000000..29f47bf5090
--- /dev/null
+++ b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.html
@@ -0,0 +1,712 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>OffscreenCanvas test: 2d.text.measure.caret-position-edges.tentative</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+
+<h1>2d.text.measure.caret-position-edges.tentative</h1>
+
+<script>
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('left' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('ltr' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('left' == 'start' || 'left' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('left' == 'start' || 'left' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align left.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('left' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('rtl' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('left' == 'start' || 'left' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('left' == 'start' || 'left' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align left.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('center' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('ltr' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('center' == 'start' || 'center' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('center' == 'start' || 'center' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align center.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('center' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('rtl' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('center' == 'start' || 'center' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('center' == 'start' || 'center' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align center.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('right' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('ltr' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('right' == 'start' || 'right' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('right' == 'start' || 'right' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align right.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('right' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('rtl' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('right' == 'start' || 'right' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('right' == 'start' || 'right' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align right.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('start' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('ltr' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('start' == 'start' || 'start' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('start' == 'start' || 'start' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'start';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align start.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('start' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('rtl' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('start' == 'start' || 'start' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('start' == 'start' || 'start' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'start';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align start.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('end' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('ltr' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('end' == 'start' || 'end' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('end' == 'start' || 'end' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'end';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align end.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('end' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('rtl' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('end' == 'start' || 'end' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('end' == 'start' || 'end' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'end';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align end.");
+
+</script>
diff --git a/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.worker.js b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.worker.js
new file mode 100644
index 00000000000..2cef8432d2a
--- /dev/null
+++ b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.caret-position-edges.tentative.worker.js
@@ -0,0 +1,709 @@
+// DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
+// OffscreenCanvas test in a worker:2d.text.measure.caret-position-edges.tentative
+// Description:
+// Note:
+
+importScripts("/resources/testharness.js");
+importScripts("/html/canvas/resources/canvas-tests.js");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('left' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('ltr' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('left' == 'start' || 'left' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('left' == 'start' || 'left' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align left.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('left' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('rtl' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('left' == 'start' || 'left' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('left' == 'start' || 'left' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align left.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('center' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('ltr' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('center' == 'start' || 'center' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('center' == 'start' || 'center' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align center.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('center' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('rtl' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('center' == 'start' || 'center' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('center' == 'start' || 'center' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align center.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('right' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('ltr' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('right' == 'start' || 'right' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('right' == 'start' || 'right' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align right.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('right' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('rtl' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('right' == 'start' || 'right' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('right' == 'start' || 'right' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align right.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('start' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('ltr' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('start' == 'start' || 'start' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('start' == 'start' || 'start' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'start';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align start.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('start' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('rtl' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('start' == 'start' || 'start' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('start' == 'start' || 'start' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'start';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align start.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('end' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('ltr' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('end' == 'start' || 'end' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('end' == 'start' || 'end' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'end';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction ltr and text align end.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('end' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('rtl' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('end' == 'start' || 'end' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('end' == 'start' || 'end' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'end';
+ ctx.letterSpacing = '';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() gives correct edges when the requested point is outside the range, with direction rtl and text align end.");
+
+done();
diff --git a/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.caret-position.tentative.html b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.caret-position.tentative.html
new file mode 100644
index 00000000000..53faccd652a
--- /dev/null
+++ b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.caret-position.tentative.html
@@ -0,0 +1,4072 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>OffscreenCanvas test: 2d.text.measure.caret-position.tentative</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+
+<h1>2d.text.measure.caret-position.tentative</h1>
+
+<script>
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('left' == 'center') {
+ offset -= width / 2;
+ } else if ('left' == 'right' ||
+ ('ltr' == 'ltr' && 'left' == 'end') ||
+ ('ltr' == 'rtl' && 'left' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('left' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('left' == 'right' ||
+ ('ltr' == 'ltr' && 'left' == 'end') ||
+ ('ltr' == 'rtl' && 'left' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 0px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('left' == 'center') {
+ offset -= width / 2;
+ } else if ('left' == 'right' ||
+ ('rtl' == 'ltr' && 'left' == 'end') ||
+ ('rtl' == 'rtl' && 'left' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('left' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('left' == 'right' ||
+ ('rtl' == 'ltr' && 'left' == 'end') ||
+ ('rtl' == 'rtl' && 'left' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 0px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('center' == 'center') {
+ offset -= width / 2;
+ } else if ('center' == 'right' ||
+ ('ltr' == 'ltr' && 'center' == 'end') ||
+ ('ltr' == 'rtl' && 'center' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('center' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('center' == 'right' ||
+ ('ltr' == 'ltr' && 'center' == 'end') ||
+ ('ltr' == 'rtl' && 'center' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 0px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('center' == 'center') {
+ offset -= width / 2;
+ } else if ('center' == 'right' ||
+ ('rtl' == 'ltr' && 'center' == 'end') ||
+ ('rtl' == 'rtl' && 'center' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('center' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('center' == 'right' ||
+ ('rtl' == 'ltr' && 'center' == 'end') ||
+ ('rtl' == 'rtl' && 'center' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 0px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('right' == 'center') {
+ offset -= width / 2;
+ } else if ('right' == 'right' ||
+ ('ltr' == 'ltr' && 'right' == 'end') ||
+ ('ltr' == 'rtl' && 'right' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('right' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('right' == 'right' ||
+ ('ltr' == 'ltr' && 'right' == 'end') ||
+ ('ltr' == 'rtl' && 'right' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 0px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('right' == 'center') {
+ offset -= width / 2;
+ } else if ('right' == 'right' ||
+ ('rtl' == 'ltr' && 'right' == 'end') ||
+ ('rtl' == 'rtl' && 'right' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('right' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('right' == 'right' ||
+ ('rtl' == 'ltr' && 'right' == 'end') ||
+ ('rtl' == 'rtl' && 'right' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 0px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('start' == 'center') {
+ offset -= width / 2;
+ } else if ('start' == 'right' ||
+ ('ltr' == 'ltr' && 'start' == 'end') ||
+ ('ltr' == 'rtl' && 'start' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('start' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('start' == 'right' ||
+ ('ltr' == 'ltr' && 'start' == 'end') ||
+ ('ltr' == 'rtl' && 'start' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'start';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 0px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('start' == 'center') {
+ offset -= width / 2;
+ } else if ('start' == 'right' ||
+ ('rtl' == 'ltr' && 'start' == 'end') ||
+ ('rtl' == 'rtl' && 'start' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('start' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('start' == 'right' ||
+ ('rtl' == 'ltr' && 'start' == 'end') ||
+ ('rtl' == 'rtl' && 'start' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'start';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 0px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('end' == 'center') {
+ offset -= width / 2;
+ } else if ('end' == 'right' ||
+ ('ltr' == 'ltr' && 'end' == 'end') ||
+ ('ltr' == 'rtl' && 'end' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('end' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('end' == 'right' ||
+ ('ltr' == 'ltr' && 'end' == 'end') ||
+ ('ltr' == 'rtl' && 'end' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'end';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 0px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('end' == 'center') {
+ offset -= width / 2;
+ } else if ('end' == 'right' ||
+ ('rtl' == 'ltr' && 'end' == 'end') ||
+ ('rtl' == 'rtl' && 'end' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('end' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('end' == 'right' ||
+ ('rtl' == 'ltr' && 'end' == 'end') ||
+ ('rtl' == 'rtl' && 'end' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'end';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 0px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('left' == 'center') {
+ offset -= width / 2;
+ } else if ('left' == 'right' ||
+ ('ltr' == 'ltr' && 'left' == 'end') ||
+ ('ltr' == 'rtl' && 'left' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('left' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('left' == 'right' ||
+ ('ltr' == 'ltr' && 'left' == 'end') ||
+ ('ltr' == 'rtl' && 'left' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 10px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('left' == 'center') {
+ offset -= width / 2;
+ } else if ('left' == 'right' ||
+ ('rtl' == 'ltr' && 'left' == 'end') ||
+ ('rtl' == 'rtl' && 'left' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('left' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('left' == 'right' ||
+ ('rtl' == 'ltr' && 'left' == 'end') ||
+ ('rtl' == 'rtl' && 'left' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 10px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('center' == 'center') {
+ offset -= width / 2;
+ } else if ('center' == 'right' ||
+ ('ltr' == 'ltr' && 'center' == 'end') ||
+ ('ltr' == 'rtl' && 'center' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('center' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('center' == 'right' ||
+ ('ltr' == 'ltr' && 'center' == 'end') ||
+ ('ltr' == 'rtl' && 'center' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 10px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('center' == 'center') {
+ offset -= width / 2;
+ } else if ('center' == 'right' ||
+ ('rtl' == 'ltr' && 'center' == 'end') ||
+ ('rtl' == 'rtl' && 'center' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('center' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('center' == 'right' ||
+ ('rtl' == 'ltr' && 'center' == 'end') ||
+ ('rtl' == 'rtl' && 'center' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 10px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('right' == 'center') {
+ offset -= width / 2;
+ } else if ('right' == 'right' ||
+ ('ltr' == 'ltr' && 'right' == 'end') ||
+ ('ltr' == 'rtl' && 'right' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('right' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('right' == 'right' ||
+ ('ltr' == 'ltr' && 'right' == 'end') ||
+ ('ltr' == 'rtl' && 'right' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 10px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('right' == 'center') {
+ offset -= width / 2;
+ } else if ('right' == 'right' ||
+ ('rtl' == 'ltr' && 'right' == 'end') ||
+ ('rtl' == 'rtl' && 'right' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('right' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('right' == 'right' ||
+ ('rtl' == 'ltr' && 'right' == 'end') ||
+ ('rtl' == 'rtl' && 'right' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 10px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('start' == 'center') {
+ offset -= width / 2;
+ } else if ('start' == 'right' ||
+ ('ltr' == 'ltr' && 'start' == 'end') ||
+ ('ltr' == 'rtl' && 'start' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('start' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('start' == 'right' ||
+ ('ltr' == 'ltr' && 'start' == 'end') ||
+ ('ltr' == 'rtl' && 'start' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'start';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 10px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('start' == 'center') {
+ offset -= width / 2;
+ } else if ('start' == 'right' ||
+ ('rtl' == 'ltr' && 'start' == 'end') ||
+ ('rtl' == 'rtl' && 'start' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('start' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('start' == 'right' ||
+ ('rtl' == 'ltr' && 'start' == 'end') ||
+ ('rtl' == 'rtl' && 'start' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'start';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 10px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('end' == 'center') {
+ offset -= width / 2;
+ } else if ('end' == 'right' ||
+ ('ltr' == 'ltr' && 'end' == 'end') ||
+ ('ltr' == 'rtl' && 'end' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('end' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('end' == 'right' ||
+ ('ltr' == 'ltr' && 'end' == 'end') ||
+ ('ltr' == 'rtl' && 'end' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'end';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 10px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('end' == 'center') {
+ offset -= width / 2;
+ } else if ('end' == 'right' ||
+ ('rtl' == 'ltr' && 'end' == 'end') ||
+ ('rtl' == 'rtl' && 'end' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('end' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('end' == 'right' ||
+ ('rtl' == 'ltr' && 'end' == 'end') ||
+ ('rtl' == 'rtl' && 'end' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'end';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 10px letter spacing and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('left' == 'center') {
+ offset -= width / 2;
+ } else if ('left' == 'right' ||
+ ('ltr' == 'ltr' && 'left' == 'end') ||
+ ('ltr' == 'rtl' && 'left' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('left' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('left' == 'right' ||
+ ('ltr' == 'ltr' && 'left' == 'end') ||
+ ('ltr' == 'rtl' && 'left' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 0px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('left' == 'center') {
+ offset -= width / 2;
+ } else if ('left' == 'right' ||
+ ('rtl' == 'ltr' && 'left' == 'end') ||
+ ('rtl' == 'rtl' && 'left' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('left' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('left' == 'right' ||
+ ('rtl' == 'ltr' && 'left' == 'end') ||
+ ('rtl' == 'rtl' && 'left' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 0px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('center' == 'center') {
+ offset -= width / 2;
+ } else if ('center' == 'right' ||
+ ('ltr' == 'ltr' && 'center' == 'end') ||
+ ('ltr' == 'rtl' && 'center' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('center' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('center' == 'right' ||
+ ('ltr' == 'ltr' && 'center' == 'end') ||
+ ('ltr' == 'rtl' && 'center' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 0px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('center' == 'center') {
+ offset -= width / 2;
+ } else if ('center' == 'right' ||
+ ('rtl' == 'ltr' && 'center' == 'end') ||
+ ('rtl' == 'rtl' && 'center' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('center' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('center' == 'right' ||
+ ('rtl' == 'ltr' && 'center' == 'end') ||
+ ('rtl' == 'rtl' && 'center' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 0px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('right' == 'center') {
+ offset -= width / 2;
+ } else if ('right' == 'right' ||
+ ('ltr' == 'ltr' && 'right' == 'end') ||
+ ('ltr' == 'rtl' && 'right' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('right' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('right' == 'right' ||
+ ('ltr' == 'ltr' && 'right' == 'end') ||
+ ('ltr' == 'rtl' && 'right' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 0px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('right' == 'center') {
+ offset -= width / 2;
+ } else if ('right' == 'right' ||
+ ('rtl' == 'ltr' && 'right' == 'end') ||
+ ('rtl' == 'rtl' && 'right' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('right' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('right' == 'right' ||
+ ('rtl' == 'ltr' && 'right' == 'end') ||
+ ('rtl' == 'rtl' && 'right' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 0px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('start' == 'center') {
+ offset -= width / 2;
+ } else if ('start' == 'right' ||
+ ('ltr' == 'ltr' && 'start' == 'end') ||
+ ('ltr' == 'rtl' && 'start' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('start' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('start' == 'right' ||
+ ('ltr' == 'ltr' && 'start' == 'end') ||
+ ('ltr' == 'rtl' && 'start' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'start';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 0px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('start' == 'center') {
+ offset -= width / 2;
+ } else if ('start' == 'right' ||
+ ('rtl' == 'ltr' && 'start' == 'end') ||
+ ('rtl' == 'rtl' && 'start' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('start' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('start' == 'right' ||
+ ('rtl' == 'ltr' && 'start' == 'end') ||
+ ('rtl' == 'rtl' && 'start' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'start';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 0px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('end' == 'center') {
+ offset -= width / 2;
+ } else if ('end' == 'right' ||
+ ('ltr' == 'ltr' && 'end' == 'end') ||
+ ('ltr' == 'rtl' && 'end' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('end' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('end' == 'right' ||
+ ('ltr' == 'ltr' && 'end' == 'end') ||
+ ('ltr' == 'rtl' && 'end' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'end';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 0px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('end' == 'center') {
+ offset -= width / 2;
+ } else if ('end' == 'right' ||
+ ('rtl' == 'ltr' && 'end' == 'end') ||
+ ('rtl' == 'rtl' && 'end' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '0px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('end' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('end' == 'right' ||
+ ('rtl' == 'ltr' && 'end' == 'end') ||
+ ('rtl' == 'rtl' && 'end' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'end';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('0px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 0px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('left' == 'center') {
+ offset -= width / 2;
+ } else if ('left' == 'right' ||
+ ('ltr' == 'ltr' && 'left' == 'end') ||
+ ('ltr' == 'rtl' && 'left' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('left' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('left' == 'right' ||
+ ('ltr' == 'ltr' && 'left' == 'end') ||
+ ('ltr' == 'rtl' && 'left' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align left, 10px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('left' == 'center') {
+ offset -= width / 2;
+ } else if ('left' == 'right' ||
+ ('rtl' == 'ltr' && 'left' == 'end') ||
+ ('rtl' == 'rtl' && 'left' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('left' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('left' == 'right' ||
+ ('rtl' == 'ltr' && 'left' == 'end') ||
+ ('rtl' == 'rtl' && 'left' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align left, 10px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('center' == 'center') {
+ offset -= width / 2;
+ } else if ('center' == 'right' ||
+ ('ltr' == 'ltr' && 'center' == 'end') ||
+ ('ltr' == 'rtl' && 'center' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('center' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('center' == 'right' ||
+ ('ltr' == 'ltr' && 'center' == 'end') ||
+ ('ltr' == 'rtl' && 'center' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align center, 10px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('center' == 'center') {
+ offset -= width / 2;
+ } else if ('center' == 'right' ||
+ ('rtl' == 'ltr' && 'center' == 'end') ||
+ ('rtl' == 'rtl' && 'center' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('center' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('center' == 'right' ||
+ ('rtl' == 'ltr' && 'center' == 'end') ||
+ ('rtl' == 'rtl' && 'center' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align center, 10px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('right' == 'center') {
+ offset -= width / 2;
+ } else if ('right' == 'right' ||
+ ('ltr' == 'ltr' && 'right' == 'end') ||
+ ('ltr' == 'rtl' && 'right' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('right' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('right' == 'right' ||
+ ('ltr' == 'ltr' && 'right' == 'end') ||
+ ('ltr' == 'rtl' && 'right' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align right, 10px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('right' == 'center') {
+ offset -= width / 2;
+ } else if ('right' == 'right' ||
+ ('rtl' == 'ltr' && 'right' == 'end') ||
+ ('rtl' == 'rtl' && 'right' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('right' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('right' == 'right' ||
+ ('rtl' == 'ltr' && 'right' == 'end') ||
+ ('rtl' == 'rtl' && 'right' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align right, 10px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('start' == 'center') {
+ offset -= width / 2;
+ } else if ('start' == 'right' ||
+ ('ltr' == 'ltr' && 'start' == 'end') ||
+ ('ltr' == 'rtl' && 'start' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('start' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('start' == 'right' ||
+ ('ltr' == 'ltr' && 'start' == 'end') ||
+ ('ltr' == 'rtl' && 'start' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'start';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align start, 10px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('start' == 'center') {
+ offset -= width / 2;
+ } else if ('start' == 'right' ||
+ ('rtl' == 'ltr' && 'start' == 'end') ||
+ ('rtl' == 'rtl' && 'start' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('start' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('start' == 'right' ||
+ ('rtl' == 'ltr' && 'start' == 'end') ||
+ ('rtl' == 'rtl' && 'start' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'start';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align start, 10px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('end' == 'center') {
+ offset -= width / 2;
+ } else if ('end' == 'right' ||
+ ('ltr' == 'ltr' && 'end' == 'end') ||
+ ('ltr' == 'rtl' && 'end' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('end' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('end' == 'right' ||
+ ('ltr' == 'ltr' && 'end' == 'end') ||
+ ('ltr' == 'rtl' && 'end' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'end';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction ltr, text align end, 10px letter spacing and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function alignOffset(offset, width) {
+ if ('end' == 'center') {
+ offset -= width / 2;
+ } else if ('end' == 'right' ||
+ ('rtl' == 'ltr' && 'end' == 'end') ||
+ ('rtl' == 'rtl' && 'end' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.letterSpacing = '10px';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('end' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('end' == 'right' ||
+ ('rtl' == 'ltr' && 'end' == 'end') ||
+ ('rtl' == 'rtl' && 'end' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ _assertSame(position.offsetNode.parentNode, el, "position.offsetNode.parentNode", "el");
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'end';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('10px' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+}, "Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent, where possible, with direction rtl, text align end, 10px letter spacing and directional-override.");
+
+</script>
diff --git a/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html
new file mode 100644
index 00000000000..8b41d0c830e
--- /dev/null
+++ b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>OffscreenCanvas test: 2d.text.measure.getActualBoundingBox-exceptions.tentative</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+
+<h1>2d.text.measure.getActualBoundingBox-exceptions.tentative</h1>
+<p class="desc">Check that TextMetrics::getActualBoundingBox() throws when using invalid indexes.</p>
+
+
+<script>
+var t = async_test("Check that TextMetrics::getActualBoundingBox() throws when using invalid indexes.");
+var t_pass = t.done.bind(t);
+var t_fail = t.step_func(function(reason) {
+ throw reason;
+});
+t.step(function() {
+
+ var canvas = new OffscreenCanvas(100, 50);
+ var ctx = canvas.getContext('2d');
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (const text of kTexts) {
+ const tm = ctx.measureText(text);
+ // Handled by the IDL binding.
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(-1, 0) );
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(0, -1) );
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(-1, -1) );
+ // Thrown in TextMetrics.
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(text.length, 0) );
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(0, text.length + 1) );
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(text.length, text.length + 1) );
+ t.done();
+
+});
+</script>
diff --git a/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.worker.js b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.worker.js
new file mode 100644
index 00000000000..84bc720399c
--- /dev/null
+++ b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-exceptions.tentative.worker.js
@@ -0,0 +1,41 @@
+// DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
+// OffscreenCanvas test in a worker:2d.text.measure.getActualBoundingBox-exceptions.tentative
+// Description:Check that TextMetrics::getActualBoundingBox() throws when using invalid indexes.
+// Note:
+
+importScripts("/resources/testharness.js");
+importScripts("/html/canvas/resources/canvas-tests.js");
+
+var t = async_test("Check that TextMetrics::getActualBoundingBox() throws when using invalid indexes.");
+var t_pass = t.done.bind(t);
+var t_fail = t.step_func(function(reason) {
+ throw reason;
+});
+t.step(function() {
+
+ var canvas = new OffscreenCanvas(100, 50);
+ var ctx = canvas.getContext('2d');
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (const text of kTexts) {
+ const tm = ctx.measureText(text);
+ // Handled by the IDL binding.
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(-1, 0) );
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(0, -1) );
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(-1, -1) );
+ // Thrown in TextMetrics.
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(text.length, 0) );
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(0, text.length + 1) );
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(text.length, text.length + 1) );
+ t.done();
+});
+done();
diff --git a/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html
new file mode 100644
index 00000000000..44b107e086d
--- /dev/null
+++ b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.html
@@ -0,0 +1,276 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>OffscreenCanvas test: 2d.text.measure.getActualBoundingBox-full-text.tentative</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+
+<h1>2d.text.measure.getActualBoundingBox-full-text.tentative</h1>
+
+<script>
+
+test(t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and no-directional-override");
+
+test(t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and no-directional-override");
+
+test(t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and directional-override");
+
+test(t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and directional-override");
+
+</script>
diff --git a/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.worker.js b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.worker.js
new file mode 100644
index 00000000000..54a4cb41154
--- /dev/null
+++ b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox-full-text.tentative.worker.js
@@ -0,0 +1,273 @@
+// DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
+// OffscreenCanvas test in a worker:2d.text.measure.getActualBoundingBox-full-text.tentative
+// Description:
+// Note:
+
+importScripts("/resources/testharness.js");
+importScripts("/html/canvas/resources/canvas-tests.js");
+
+test(t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and no-directional-override");
+
+test(t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and no-directional-override");
+
+test(t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction ltr and directional-override");
+
+test(t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox() for the full length of the string for some edge cases, with direction rtl and directional-override");
+
+done();
diff --git a/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.html b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.html
new file mode 100644
index 00000000000..e6a2b070205
--- /dev/null
+++ b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.html
@@ -0,0 +1,510 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>OffscreenCanvas test: 2d.text.measure.getActualBoundingBox.tentative</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+
+<h1>2d.text.measure.getActualBoundingBox.tentative</h1>
+
+<script>
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ document.fonts.add(f);
+ await document.fonts.ready;
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '0px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align left , and 0px letter spacing.");
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ document.fonts.add(f);
+ await document.fonts.ready;
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '0px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align center , and 0px letter spacing.");
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ document.fonts.add(f);
+ await document.fonts.ready;
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '0px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align right , and 0px letter spacing.");
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ document.fonts.add(f);
+ await document.fonts.ready;
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '10px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align left , and 10px letter spacing.");
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ document.fonts.add(f);
+ await document.fonts.ready;
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '10px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align center , and 10px letter spacing.");
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ document.fonts.add(f);
+ await document.fonts.ready;
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '10px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align right , and 10px letter spacing.");
+
+</script>
diff --git a/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.worker.js b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.worker.js
new file mode 100644
index 00000000000..9530d177658
--- /dev/null
+++ b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.worker.js
@@ -0,0 +1,507 @@
+// DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
+// OffscreenCanvas test in a worker:2d.text.measure.getActualBoundingBox.tentative
+// Description:
+// Note:
+
+importScripts("/resources/testharness.js");
+importScripts("/html/canvas/resources/canvas-tests.js");
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ self.fonts.add(f);
+ await self.fonts.ready;
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '0px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align left , and 0px letter spacing.");
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ self.fonts.add(f);
+ await self.fonts.ready;
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '0px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align center , and 0px letter spacing.");
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ self.fonts.add(f);
+ await self.fonts.ready;
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '0px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align right , and 0px letter spacing.");
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ self.fonts.add(f);
+ await self.fonts.ready;
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '10px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align left , and 10px letter spacing.");
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ self.fonts.add(f);
+ await self.fonts.ready;
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '10px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align center , and 10px letter spacing.");
+
+promise_test(async t => {
+ const canvas = new OffscreenCanvas(800, 200);
+ const ctx = canvas.getContext('2d');
+
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ f.load();
+ self.fonts.add(f);
+ await self.fonts.ready;
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '10px';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+}, "Test TextMetrics::getActualBoundingBox(), with text align right , and 10px letter spacing.");
+
+done();
diff --git a/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects-baselines.tentative.html b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects-baselines.tentative.html
index 818a63ef65f..23220d4a446 100644
--- a/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects-baselines.tentative.html
+++ b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects-baselines.tentative.html
@@ -34,7 +34,8 @@ t.step(function() {
const kTexts = [
'UNAVAILABLE',
'🏁🎶🏁',
- ')(あ)('
+ ')(あ)(',
+ '-abcd_'
]
for (const text of kTexts) {
diff --git a/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects-baselines.tentative.worker.js b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects-baselines.tentative.worker.js
index 56a8d2799a0..6d275c042eb 100644
--- a/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects-baselines.tentative.worker.js
+++ b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects-baselines.tentative.worker.js
@@ -30,7 +30,8 @@ t.step(function() {
const kTexts = [
'UNAVAILABLE',
'🏁🎶🏁',
- ')(あ)('
+ ')(あ)(',
+ '-abcd_'
]
for (const text of kTexts) {
diff --git a/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects-exceptions.tentative.html b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects-exceptions.tentative.html
index 06ffeacd037..518fbc654aa 100644
--- a/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects-exceptions.tentative.html
+++ b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects-exceptions.tentative.html
@@ -23,7 +23,8 @@ t.step(function() {
const kTexts = [
'UNAVAILABLE',
'🏁🎶🏁',
- ')(あ)('
+ ')(あ)(',
+ '-abcd_'
]
for (const text of kTexts) {
diff --git a/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects-exceptions.tentative.worker.js b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects-exceptions.tentative.worker.js
index 1ec8bc49731..a3fefb3b253 100644
--- a/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects-exceptions.tentative.worker.js
+++ b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects-exceptions.tentative.worker.js
@@ -19,7 +19,8 @@ t.step(function() {
const kTexts = [
'UNAVAILABLE',
'🏁🎶🏁',
- ')(あ)('
+ ')(あ)(',
+ '-abcd_'
]
for (const text of kTexts) {
diff --git a/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects.tentative.html b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects.tentative.html
index e8574850fac..6c0261e1534 100644
--- a/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects.tentative.html
+++ b/tests/wpt/tests/html/canvas/offscreen/text/2d.text.measure.selection-rects.tentative.html
@@ -6,24 +6,2108 @@
<script src="/html/canvas/resources/canvas-tests.js"></script>
<h1>2d.text.measure.selection-rects.tentative</h1>
-<p class="desc">Check that TextMetrics::getSelectionRects() matches its DOM equivalent.</p>
-
<script>
-var t = async_test("Check that TextMetrics::getSelectionRects() matches its DOM equivalent.");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
- throw reason;
-});
-t.step(function() {
- var canvas = new OffscreenCanvas(100, 50);
- var ctx = canvas.getContext('2d');
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'left';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 0px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'left';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 0px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'center';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 0px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'center';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 0px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'right';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 0px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'right';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 0px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'left';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 10px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'left';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 10px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'center';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 10px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'center';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 10px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'right';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 10px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'right';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 10px letter spacing, and no-directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'left';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 0px letter spacing, and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'left';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 0px letter spacing, and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'center';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 0px letter spacing, and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'center';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 0px letter spacing, and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'right';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 0px letter spacing, and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'right';
+ el.style.letterSpacing = '0px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '0px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 0px letter spacing, and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'left';
+ el.style.letterSpacing = '10px';
document.body.appendChild(el);
let range = document.createRange();
@@ -31,6 +2115,10 @@ t.step(function() {
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
@@ -43,8 +2131,21 @@ t.step(function() {
document.body.removeChild(el);
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
for(let i = 0 ; i < sel_rects.length ; ++i) {
- sel_rects[i].x -= parent.x;
+ sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
@@ -62,15 +2163,651 @@ t.step(function() {
}
}
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align left, 10px letter spacing, and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'left';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'left';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align left, 10px letter spacing, and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'center';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align center, 10px letter spacing, and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'center';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'center';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align center, 10px letter spacing, and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'ltr';
+ el.style.textAlign = 'right';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = 'ltr';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '10px';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ const tm = ctx.measureText(text);
+ // First character.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, 1),
+ placeAndSelectTextInDOM(text, 0, 1)
+ );
+ // Last character.
+ checkRectListsMatch(
+ tm.getSelectionRects(text.length - 1, text.length),
+ placeAndSelectTextInDOM(text, text.length - 1, text.length)
+ );
+ // Whole string.
+ checkRectListsMatch(
+ tm.getSelectionRects(0, text.length),
+ placeAndSelectTextInDOM(text, 0, text.length)
+ );
+ // Intermediate string.
+ checkRectListsMatch(
+ tm.getSelectionRects(1, text.length - 1),
+ placeAndSelectTextInDOM(text, 1, text.length - 1)
+ );
+ // Invalid start > end string. Creates 0 width rectangle.
+ checkRectListsMatch(
+ tm.getSelectionRects(3, 2),
+ placeAndSelectTextInDOM(text, 3, 2)
+ );
+ checkRectListsMatch(
+ tm.getSelectionRects(1, 0),
+ placeAndSelectTextInDOM(text, 1, 0)
+ );
+ }
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction ltr, text align right, 10px letter spacing, and directional-override.");
+
+test(t => {
+ const canvas = new OffscreenCanvas(100, 50);
+ const ctx = canvas.getContext('2d');
+
+ function placeAndSelectTextInDOM(text, from, to) {
+ const el = document.createElement("div");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = 'rtl';
+ el.style.textAlign = 'right';
+ el.style.letterSpacing = '10px';
+ document.body.appendChild(el);
+
+ let range = document.createRange();
+
+ range.setStart(el.childNodes[0], 0);
+ range.setEnd(el.childNodes[0], text.length);
+ const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
+
+ range.setStart(el.childNodes[0], from);
+ range.setEnd(el.childNodes[0], to);
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+
+ let sel_rects = sel.getRangeAt(0).getClientRects();
+
+ document.body.removeChild(el);
+
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
+ for(let i = 0 ; i < sel_rects.length ; ++i) {
+ sel_rects[i].x -= parent.x + text_align_dx;
+ sel_rects[i].y -= parent.y;
+ }
+
+ return sel_rects;
+ }
+
+ function checkRectListsMatch(list_a, list_b) {
+ _assertSame(list_a.length, list_b.length, "list_a.length", "list_b.length");
+ for(let i = 0 ; i < list_a.length ; ++i) {
+ assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
+ assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
+ assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
+ // Y-position not tested here as getting the baseline for text in the
+ // DOM is not straightforward.
+ }
+ }
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+
ctx.font = '50px sans-serif';
+ ctx.direction = 'rtl';
+ ctx.textAlign = 'right';
+ ctx.letterSpacing = '10px';
const kTexts = [
'UNAVAILABLE',
'🏁🎶🏁',
- ')(あ)('
+ ')(あ)(',
+ '-abcd_'
]
- for (const text of kTexts) {
+ for (text of kTexts) {
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
@@ -102,7 +2839,6 @@ t.step(function() {
placeAndSelectTextInDOM(text, 1, 0)
);
}
- t.done();
+}, "Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with direction rtl, text align right, 10px letter spacing, and directional-override.");
-});
</script>
diff --git a/tests/wpt/tests/html/canvas/tools/yaml-new/text.yaml b/tests/wpt/tests/html/canvas/tools/yaml-new/text.yaml
index 49edd754c85..240a04ce8dc 100644
--- a/tests/wpt/tests/html/canvas/tools/yaml-new/text.yaml
+++ b/tests/wpt/tests/html/canvas/tools/yaml-new/text.yaml
@@ -235,8 +235,9 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: &load-font-variant-definition
- - HtmlCanvas:
+ variants:
+ - &load-font-variant-definition
+ HtmlCanvas:
append_variants_to_name: false
canvas_types: ['HtmlCanvas']
load_font: |-
@@ -271,7 +272,8 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.baseline.middle
desc: textBaseline middle is the middle of the em square (not the bounding box)
@@ -293,7 +295,8 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.baseline.alphabetic
test_type: promise
@@ -314,7 +317,8 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.baseline.ideographic
test_type: promise
@@ -335,7 +339,8 @@
@assert pixel 5,45 ==~ 0,255,0,255; @moz-todo
@assert pixel 95,45 ==~ 0,255,0,255; @moz-todo
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.baseline.hanging
test_type: promise
@@ -356,7 +361,8 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.space.collapse.space
desc: Space characters are converted to U+0020, and are NOT collapsed
@@ -373,7 +379,8 @@
@assert pixel 25,25 ==~ 0,255,0,255;
@assert pixel 75,25 ==~ 255,0,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.space.collapse.other
desc: Space characters are converted to U+0020, and are NOT collapsed
@@ -390,7 +397,8 @@
@assert pixel 25,25 ==~ 0,255,0,255;
@assert pixel 75,25 ==~ 255,0,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.space.collapse.start
desc: Space characters at the start of a line are NOT collapsed
@@ -407,7 +415,8 @@
@assert pixel 25,25 ==~ 255,0,0,255; @moz-todo
@assert pixel 75,25 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.space.collapse.end
desc: Space characters at the end of a line are NOT collapsed
@@ -425,7 +434,8 @@
@assert pixel 25,25 ==~ 0,255,0,255;
@assert pixel 75,25 ==~ 255,0,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.width.space
desc: Space characters are converted to U+0020 and NOT collapsed
@@ -442,7 +452,8 @@
@assert ctx.measureText(' AB').width === 150;
@assert ctx.measureText('AB ').width === 150;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.drawing.style.measure.rtl.text
desc: Measurement should follow canvas direction instead text direction
@@ -704,7 +715,8 @@
@assert pixel 25,25 ==~ 0,255,0,255;
@assert pixel 75,25 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.fill.maxWidth.bound
desc: fillText handles maxWidth based on line size, not bounding box size
@@ -723,7 +735,8 @@
@assert pixel 25,25 ==~ 0,255,0,255;
@assert pixel 75,25 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.fontface
fonts:
@@ -741,7 +754,8 @@
@assert pixel 25,25 ==~ 0,255,0,255;
@assert pixel 75,25 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.fontface.repeat
desc: Draw with the font immediately, then wait a bit until and draw again. (This
@@ -765,7 +779,8 @@
_assertPixelApprox(canvas, 25,25, 0,255,0,255, 2);
_assertPixelApprox(canvas, 75,25, 0,255,0,255, 2);
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.fontface.notinpage
desc: '@font-face fonts should work even if they are not used in the page'
@@ -785,7 +800,8 @@
@assert pixel 25,25 ==~ 0,255,0,255; @moz-todo
@assert pixel 75,25 ==~ 0,255,0,255; @moz-todo
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.align.left
desc: textAlign left is the left of the first em square (not the bounding box)
@@ -807,7 +823,8 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.align.right
desc: textAlign right is the right of the last em square (not the bounding box)
@@ -829,7 +846,8 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.align.start.ltr
desc: textAlign start with ltr is the left edge
@@ -855,7 +873,8 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.align.start.rtl
desc: textAlign start with rtl is the right edge
@@ -881,7 +900,8 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.align.end.ltr
desc: textAlign end with ltr is the right edge
@@ -907,7 +927,8 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.align.end.rtl
desc: textAlign end with rtl is the left edge
@@ -933,7 +954,8 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.align.center
desc: textAlign center is the center of the em squares (not the bounding box)
@@ -955,7 +977,8 @@
@assert pixel 5,45 ==~ 0,255,0,255;
@assert pixel 95,45 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.space.basic
@@ -973,7 +996,8 @@
@assert pixel 25,25 ==~ 0,255,0,255;
@assert pixel 75,25 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.draw.space.collapse.nonspace
desc: Non-space characters are not converted to U+0020 and collapsed
@@ -990,7 +1014,8 @@
@assert pixel 25,25 ==~ 0,255,0,255;
@assert pixel 75,25 ==~ 0,255,0,255;
expected: green
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.width.basic
desc: The width of character is same as font used
@@ -1006,7 +1031,8 @@
ctx.font = '100px CanvasTest';
@assert ctx.measureText('A').width === 100;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.width.empty
desc: The empty string has zero width
@@ -1017,7 +1043,8 @@
{{ load_font }}
ctx.font = '50px CanvasTest';
@assert ctx.measureText("").width === 0;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.actualBoundingBox
desc: Testing actualBoundingBox
@@ -1052,7 +1079,8 @@
@assert ctx.measureText('ABCD').actualBoundingBoxRight >= 200;
@assert ctx.measureText('ABCD').actualBoundingBoxAscent >= 85;
@assert ctx.measureText('ABCD').actualBoundingBoxDescent >= 37;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.fontBoundingBox
desc: Testing fontBoundingBox measurements
@@ -1069,7 +1097,8 @@
@assert ctx.measureText('ABCD').fontBoundingBoxAscent === 30;
@assert ctx.measureText('ABCD').fontBoundingBoxDescent === 10;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.fontBoundingBox.ahem
desc: Testing fontBoundingBox for font ahem
@@ -1085,7 +1114,8 @@
@assert ctx.measureText('A').fontBoundingBoxDescent === 10;
@assert ctx.measureText('ABCD').fontBoundingBoxAscent === 40;
@assert ctx.measureText('ABCD').fontBoundingBoxDescent === 10;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.fontBoundingBox-reduced-ascent
desc: Testing fontBoundingBox for OffscreenCanvas with reduced ascent metric
@@ -1102,7 +1132,8 @@
@assert ctx.measureText('ABCD').fontBoundingBoxAscent === 10;
@assert ctx.measureText('ABCD').fontBoundingBoxDescent === 10;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.fontBoundingBox-zero-descent
desc: Testing fontBoundingBox for OffscreenCanvas with zero descent metric
@@ -1119,7 +1150,8 @@
@assert ctx.measureText('ABCD').fontBoundingBoxAscent === 30;
@assert ctx.measureText('ABCD').fontBoundingBoxDescent === 0;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.emHeights
desc: Testing emHeights
@@ -1138,7 +1170,8 @@
@assert ctx.measureText('ABCD').emHeightAscent === 30;
@assert ctx.measureText('ABCD').emHeightDescent === 10;
@assert ctx.measureText('ABCD').emHeightDescent + ctx.measureText('ABCD').emHeightAscent === 40;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.emHeights-low-ascent
desc: Testing emHeights with reduced ascent metric
@@ -1157,7 +1190,8 @@
@assert ctx.measureText('ABCD').emHeightAscent === 20;
@assert ctx.measureText('ABCD').emHeightDescent === 20;
@assert ctx.measureText('ABCD').emHeightDescent + ctx.measureText('ABCD').emHeightAscent === 40;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.emHeights-zero-descent
desc: Testing emHeights with zero descent metric
@@ -1176,7 +1210,8 @@
@assert ctx.measureText('ABCD').emHeightAscent === 40;
@assert ctx.measureText('ABCD').emHeightDescent === 0;
@assert ctx.measureText('ABCD').emHeightDescent + ctx.measureText('ABCD').emHeightAscent === 40;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.baselines
desc: Testing baselines
@@ -1195,16 +1230,23 @@
@assert Math.abs(ctx.measureText('ABCD').alphabeticBaseline) === 0;
@assert ctx.measureText('ABCD').ideographicBaseline === 6.25;
@assert ctx.measureText('ABCD').hangingBaseline === 25;
- variants: *load-font-variant-definition
+ variants:
+ - *load-font-variant-definition
- name: 2d.text.measure.selection-rects.tentative
- desc: Check that TextMetrics::getSelectionRects() matches its DOM equivalent.
+ desc: >-
+ Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with
+ direction {{ text_direction }}, text align {{ text_align }},
+ {{ letter_spacing }} letter spacing, and {{ variant_names[3] }}.
canvas_types: ['HtmlCanvas', 'OffscreenCanvas']
code: |
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
+ el.style.direction = '{{ text_direction }}';
+ el.style.textAlign = '{{ text_align }}';
+ el.style.letterSpacing = '{{ letter_spacing }}';
document.body.appendChild(el);
let range = document.createRange();
@@ -1212,6 +1254,10 @@
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
+ let width = 0;
+ for (const rect of range.getClientRects()) {
+ width += rect.width;
+ }
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
@@ -1224,8 +1270,21 @@
document.body.removeChild(el);
+ // Offset to the alignment point determined by textAlign.
+ let text_align_dx;
+ switch (el.style.textAlign) {
+ case 'right':
+ text_align_dx = width;
+ break;
+ case 'center':
+ text_align_dx = width / 2;
+ break;
+ default:
+ text_align_dx = 0;
+ }
+
for(let i = 0 ; i < sel_rects.length ; ++i) {
- sel_rects[i].x -= parent.x;
+ sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
@@ -1242,16 +1301,36 @@
// DOM is not straightforward.
}
}
+ {% if use_directional_override %}
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+ {% endif %}
ctx.font = '50px sans-serif';
+ ctx.direction = '{{ text_direction }}';
+ ctx.textAlign = '{{ text_align }}';
+ ctx.letterSpacing = '{{ letter_spacing }}';
const kTexts = [
'UNAVAILABLE',
'🏁🎶🏁',
- ')(あ)('
+ ')(あ)(',
+ '-abcd_'
]
- for (const text of kTexts) {
+ for (text of kTexts) {
+ {% if use_directional_override %}
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ {% endif %}
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
@@ -1283,6 +1362,26 @@
placeAndSelectTextInDOM(text, 1, 0)
);
}
+ variants_layout: [single_file, single_file, single_file, single_file]
+ variants:
+ - direction-ltr:
+ text_direction: ltr
+ direction-rtl:
+ text_direction: rtl
+ - align-left:
+ text_align: left
+ align-center:
+ text_align: center
+ align-right:
+ text_align: right
+ - no-spacing:
+ letter_spacing: 0px
+ spacing:
+ letter_spacing: 10px
+ - no-directional-override:
+ use_directional_override: false
+ directional-override:
+ use_directional_override: true
- name: 2d.text.measure.selection-rects-baselines.tentative
desc: >-
@@ -1303,7 +1402,8 @@
const kTexts = [
'UNAVAILABLE',
'🏁🎶🏁',
- ')(あ)('
+ ')(あ)(',
+ '-abcd_'
]
for (const text of kTexts) {
@@ -1349,7 +1449,8 @@
const kTexts = [
'UNAVAILABLE',
'🏁🎶🏁',
- ')(あ)('
+ ')(あ)(',
+ '-abcd_'
]
for (const text of kTexts) {
@@ -1367,6 +1468,478 @@
() => tm.getSelectionRects(text.length + 1, text.length + 1) );
}
+- name: 2d.text.measure.getActualBoundingBox.tentative
+ desc: >-
+ Test TextMetrics::getActualBoundingBox(), with text align {{ text_align }}
+ , and {{ letter_spacing }} letter spacing.
+ test_type: promise
+ fonts:
+ - CanvasTest
+ size: [800, 200]
+ code: |
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ // Returns a string a replaces the characters in text that are not in the
+ // range [start, end) with spaces.
+ function buildTestString(text, start, end) {
+ let ret = '';
+ for (let i = 0; i < text.length; ++i) {
+ if (start <= i && i < end)
+ ret += text[i];
+ else
+ ret += ' ';
+ }
+ return ret;
+ }
+
+ function checkRectsMatch(rect_a, rect_b, text, start, end) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
+ }
+
+ function testForSubstring(text, start, end) {
+ const textMetrics = ctx.measureText(text);
+ const rect_from_api = textMetrics.getActualBoundingBox(start, end);
+ const rect_from_full_bounds = getFullTextBoundingBox(
+ buildTestString(text, start, end));
+ checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
+ }
+
+ {{ load_font }}
+ ctx.textAlign = '{{ text_align }}';
+ ctx.letterSpacing = '{{ letter_spacing }}';
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ ctx.font = '50px CanvasTest';
+ const text = 'ABCDE';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ // Full string.
+ testForSubstring(text, 0, text.length);
+ // Intermediate string.
+ testForSubstring(text, 1, text.length - 1);
+ // First character.
+ testForSubstring(text, 0, 1);
+ // Intermediate character.
+ testForSubstring(text, 2, 3);
+ }
+ }
+ variants_layout:
+ [multi_files, single_file, single_file]
+ variants:
+ - *load-font-variant-definition
+ - align-left:
+ text_align: left
+ align-center:
+ text_align: center
+ align-right:
+ text_align: right
+ - no-spacing:
+ letter_spacing: 0px
+ spacing:
+ letter_spacing: 10px
+
+- name: 2d.text.measure.getActualBoundingBox-full-text.tentative
+ desc: >-
+ Test TextMetrics::getActualBoundingBox() for the full length of the string
+ for some edge cases, with direction {{ text_direction }} and
+ {{ variant_names[1] }}
+ size: [800, 200]
+ code: |
+ // Use measureText to create a rect for the whole text
+ function getFullTextBoundingBox(text) {
+ const tm = ctx.measureText(text);
+ return {
+ x: -tm.actualBoundingBoxLeft,
+ y: -tm.actualBoundingBoxAscent,
+ width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
+ height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
+ };
+ }
+
+ function checkRectsMatch(rect_a, rect_b) {
+ assert_approx_equals(rect_a.x, rect_b.x, 1.0);
+ assert_approx_equals(rect_a.y, rect_b.y, 1.0);
+ assert_approx_equals(rect_a.width, rect_b.width, 1.0);
+ assert_approx_equals(rect_a.height, rect_b.height, 1.0);
+ }
+ {% if use_directional_override %}
+
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+ {% endif %}
+
+ const kAligns = [
+ 'left',
+ 'center',
+ 'right',
+ ];
+
+ const kBaselines = [
+ 'top',
+ 'hanging',
+ 'middle',
+ 'alphabetic',
+ 'ideographic',
+ 'bottom',
+ ];
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd '
+ ]
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = '{{ text_direction }}';
+ for (const align of kAligns) {
+ for (const baseline of kBaselines) {
+ ctx.textAlign = align;
+ ctx.textBaseline = baseline;
+ for (text of kTexts) {
+ {% if use_directional_override %}
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ {% endif %}
+ const tm = ctx.measureText(text);
+ const rect_from_api = tm.getActualBoundingBox(0, text.length);
+ const rect_from_full_bounds = getFullTextBoundingBox(text);
+ checkRectsMatch(rect_from_api, rect_from_full_bounds)
+ }
+ }
+ }
+ variants_layout: [single_file, single_file]
+ variants:
+ - direction-ltr:
+ text_direction: ltr
+ direction-rtl:
+ text_direction: rtl
+ - no-directional-override:
+ use_directional_override: false
+ directional-override:
+ use_directional_override: true
+
+- name: 2d.text.measure.getActualBoundingBox-exceptions.tentative
+ desc: >-
+ Check that TextMetrics::getActualBoundingBox() throws when using invalid
+ indexes.
+ code: |
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '-abcd_'
+ ]
+
+ for (const text of kTexts) {
+ const tm = ctx.measureText(text);
+ // Handled by the IDL binding.
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(-1, 0) );
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(0, -1) );
+ assert_throws_js(TypeError, () => tm.getActualBoundingBox(-1, -1) );
+ // Thrown in TextMetrics.
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(text.length, 0) );
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(0, text.length + 1) );
+ assert_throws_dom("IndexSizeError",
+ () => tm.getActualBoundingBox(text.length, text.length + 1) );
+
+- name: 2d.text.measure.caret-position.tentative
+ desc: >-
+ Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent,
+ where possible, with direction {{ text_direction }}, text align
+ {{ text_align }}, {{ letter_spacing }} letter spacing and
+ {{ variant_names[3] }}.
+ canvas_types: ['HtmlCanvas', 'OffscreenCanvas']
+ code: |
+ function alignOffset(offset, width) {
+ if ('{{ text_align }}' == 'center') {
+ offset -= width / 2;
+ } else if ('{{ text_align }}' == 'right' ||
+ ('{{ text_direction }}' == 'ltr' && '{{ text_align }}' == 'end') ||
+ ('{{ text_direction }}' == 'rtl' && '{{ text_align }}' == 'start')) {
+ offset -= width;
+ }
+ return offset;
+ }
+
+ {% if use_directional_override %}
+ function addDirectionalOverrideCharacters(text, direction_is_ltr) {
+ // source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
+ const LTR_OVERRIDE = '\u202D';
+ const RTL_OVERRIDE = '\u202E';
+ const OVERRIDE_END = '\u202C';
+ if (direction_is_ltr) {
+ return LTR_OVERRIDE + text + OVERRIDE_END;
+ }
+ return RTL_OVERRIDE + text + OVERRIDE_END;
+ }
+ {% endif %}
+
+ function placeAndMeasureTextInDOM(text, text_width, point) {
+ const el = document.createElement("p");
+ el.innerHTML = text;
+ el.style.font = '50px sans-serif';
+ el.style.direction = '{{ text_direction }}';
+ el.style.letterSpacing = '{{ letter_spacing }}';
+ // Put the text top left to make offsets simpler.
+ el.style.padding = '0px';
+ el.style.margin = '0px';
+ el.style.position = 'absolute';
+ el.style.x = '0px';
+ el.style.y = '0px';
+ document.body.appendChild(el);
+ text_bound = el.getBoundingClientRect();
+ text_x = text_bound.x;
+ text_y = text_bound.y + text_bound.height / 2;
+
+ // Offset to the requested point determined by textAlign and direction.
+ let text_align_dx = 0;
+ if ('{{ text_align }}' == 'center') {
+ text_align_dx = text_width / 2;
+ } else if ('{{ text_align }}' == 'right' ||
+ ('{{ text_direction }}' == 'ltr' && '{{ text_align }}' == 'end') ||
+ ('{{ text_direction }}' == 'rtl' && '{{ text_align }}' == 'start')) {
+ text_align_dx = text_width;
+ }
+ position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
+ @assert position.offsetNode.parentNode === el;
+
+ document.body.removeChild(el);
+
+ return position.offset;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = '{{ text_direction }}';
+ ctx.textAlign = '{{ text_align }}';
+ ctx.letterSpacing = '{{ letter_spacing }}';
+
+ const kTexts = [
+ 'UNAVAILABLE',
+ '🏁🎶🏁',
+ ')(あ)(',
+ '--abcd__'
+ ]
+
+ for (text of kTexts) {
+ {% if use_directional_override %}
+ text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
+ {% endif %}
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ step = 30;
+ if ('{{letter_spacing}}' == '10px') {
+ step = 40;
+ }
+
+ offset = step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width / 2;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+
+ offset = text_width - step;
+ adjusted_offset = alignOffset(offset, text_width);
+ tm_position = tm.caretPositionFromPoint(adjusted_offset);
+ doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
+ assert_equals(tm_position,
+ doc_position,
+ "for " + text + " offset " + offset);
+ }
+ variants_layout: [single_file, single_file, single_file, single_file]
+ variants:
+ - direction-ltr:
+ text_direction: ltr
+ direction-rtl:
+ text_direction: rtl
+ - align-left:
+ text_align: left
+ align-center:
+ text_align: center
+ align-right:
+ text_align: right
+ align-start:
+ text_align: start
+ align-end:
+ text_align: end
+ - no-spacing:
+ letter_spacing: 0px
+ spacing:
+ letter_spacing: 10px
+ - no-directional-override:
+ use_directional_override: false
+ directional-override:
+ use_directional_override: true
+
+- name: 2d.text.measure.caret-position-edges.tentative
+ desc: >-
+ Check that TextMetrics::caretPositionFromPoint() gives correct edges when
+ the requested point is outside the range, with direction {{ text_direction }}
+ and text align {{ text_align }}.
+ code: |
+ function computeExpected(text, text_width, offset) {
+ expected_position = 0;
+ if ('{{ text_align }}' == 'center' && offset == 0) {
+ return text.length / 2;
+ }
+ if ('{{ text_direction }}' == 'ltr') {
+ if (offset >= text_width) {
+ return text.length;
+ }
+ if (offset <= -text_width) {
+ return 0;
+ }
+ // offset must be 0.
+ if ('{{ text_align }}' == 'start' || '{{ text_align }}' == 'left') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ } else {
+ if (offset >= text_width) {
+ return 0;
+ }
+ if (offset <= -text_width) {
+ return text.length;
+ }
+ // offset must be 0.
+ if ('{{ text_align }}' == 'start' || '{{ text_align }}' == 'right') {
+ return 0;
+ } else {
+ return text.length;
+ }
+ }
+ return expected_position;
+ }
+
+ ctx.font = '50px sans-serif';
+ ctx.direction = '{{ text_direction }}';
+ ctx.textAlign = '{{ text_align }}';
+ ctx.letterSpacing = '{{ letter_spacing }}';
+
+ // The leading and trailing '-' cause the string to always follow
+ // the specified direction, even though the interior will always be ltr.
+ const text = '-0123456789-';
+
+ // Points are multiples of the string width as reported by
+ // textMetrics.width.
+ const kPoints = [
+ -2,
+ -1,
+ 0,
+ 1,
+ 2
+ ]
+
+ const tm = ctx.measureText(text);
+ text_width = tm.width;
+ for (const multiple of kPoints) {
+ offset = multiple * text_width;
+ tm_position = tm.caretPositionFromPoint(offset);
+ expected_position = computeExpected(text, text_width, offset);
+ assert_equals(tm_position,
+ expected_position,
+ "for " + text + " multiple " + multiple);
+ }
+ variants_layout: [single_file, single_file]
+ variants:
+ - direction-ltr:
+ text_direction: ltr
+ direction-rtl:
+ text_direction: rtl
+ - align-left:
+ text_align: left
+ align-center:
+ text_align: center
+ align-right:
+ text_align: right
+ align-start:
+ text_align: start
+ align-end:
+ text_align: end
+
+- name: 2d.text.measure.caret-position-edge-cases.tentative
+ desc: >-
+ Test the edge cases for caretPositionFromPoint, where the point is at the
+ edge of glyph and at the midpoint.
+ test_type: promise
+ fonts:
+ - CanvasTest
+ code: |
+ {{ load_font }}
+ ctx.font = '50px CanvasTest';
+ ctx.direction = 'ltr';
+ ctx.align = 'left'
+ ctx.baseline = 'alphabetic'
+ tm = ctx.measureText('A');
+ const a_width = tm.width;
+ tm = ctx.measureText('B');
+ const b_width = tm.width;
+ tm = ctx.measureText('C');
+ const c_width = tm.width;
+ const epsilon = 1.0e-4;
+
+ tm = ctx.measureText('ABC');
+ @assert tm.caretPositionFromPoint(0) == 0;
+ @assert tm.caretPositionFromPoint(a_width / 2) == 0;
+ @assert tm.caretPositionFromPoint(a_width / 2 + 1) == 1;
+ @assert tm.caretPositionFromPoint(a_width) == 1;
+ @assert tm.caretPositionFromPoint(a_width + b_width / 2) == 1;
+ @assert tm.caretPositionFromPoint(a_width + b_width / 2 + 1) == 2;
+ @assert tm.caretPositionFromPoint(a_width + b_width) == 2;
+ @assert tm.caretPositionFromPoint(a_width + b_width + c_width / 2) == 2;
+ @assert tm.caretPositionFromPoint(a_width + b_width + c_width / 2 + 1) == 3;
+ @assert tm.caretPositionFromPoint(a_width + b_width + c_width) == 3;
+ variants:
+ - *load-font-variant-definition
+
- name: 2d.text.drawing.style.absolute.spacing
desc: Testing letter spacing and word spacing with absolute length
code: |
diff --git a/tests/wpt/tests/html/cross-origin-opener-policy/reporting/resources/reporting-common.js b/tests/wpt/tests/html/cross-origin-opener-policy/reporting/resources/reporting-common.js
index 70bb4897f50..accb6faaec5 100644
--- a/tests/wpt/tests/html/cross-origin-opener-policy/reporting/resources/reporting-common.js
+++ b/tests/wpt/tests/html/cross-origin-opener-policy/reporting/resources/reporting-common.js
@@ -378,6 +378,8 @@ const coopHeaders = function (uuid) {
getHeader(uuid, "same-origin-allow-popups", is_report_only = true),
coopReportOnlyRestrictPropertiesHeader:
getHeader(uuid, "restrict-properties", is_report_only = true),
+ coopReportOnlyNoopenerAllowPopupsHeader:
+ getHeader(uuid, "noopener-allow-popups", is_report_only = true),
};
}
diff --git a/tests/wpt/tests/html/cross-origin-opener-policy/reporting/tentative/access-to-noopener-page-from-no-coop-ro.https.html b/tests/wpt/tests/html/cross-origin-opener-policy/reporting/tentative/access-to-noopener-page-from-no-coop-ro.https.html
new file mode 100644
index 00000000000..5e407de3482
--- /dev/null
+++ b/tests/wpt/tests/html/cross-origin-opener-policy/reporting/tentative/access-to-noopener-page-from-no-coop-ro.https.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<title>
+ COOP reports are sent when the openee used COOP-RO+COEP and then its
+ same-origin opener tries to access it.
+</title>
+<meta name=timeout content=long>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/common/get-host-info.sub.js></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/html/cross-origin-opener-policy/reporting/resources/reporting-common.js"></script>
+<script src="/html/cross-origin-opener-policy/reporting/resources/try-access.js"></script>
+<script>
+
+const directory = "/html/cross-origin-opener-policy";
+const redirect_path = directory + "/resources/redirect.py?";
+const same_origin = get_host_info().HTTPS_ORIGIN;
+
+let runTest = (openee_redirect, name) => promise_test(async t => {
+ const report_token = token();
+ const openee_token = token();
+ const opener_token = token(); // The current test window.
+
+ const opener_url = location.href;
+
+ const reportTo = reportToHeaders(report_token);
+ const openee_url = same_origin + executor_path + reportTo.header +
+ reportTo.coopReportOnlyNoopenerAllowPopupsHeader + coep_header +
+ `&uuid=${openee_token}`;
+ const openee_redirect_url = same_origin + redirect_path + openee_url
+ const openee_requested_url = openee_redirect ? openee_redirect_url
+ : openee_url;
+
+
+ const openee = window.open(openee_requested_url);
+ t.add_cleanup(() => send(openee_token, "window.close()"))
+
+ // 1. Make sure the new document to be loaded.
+ send(openee_token, `
+ send("${opener_token}", "Ready");
+ `);
+ let reply = await receive(opener_token);
+ assert_equals(reply, "Ready");
+
+ // 2. Try to access the openee. A report is sent, because of COOP-RO+COEP.
+ tryAccess(openee);
+
+ // 3. Check a report is sent to the openee.
+ let report =
+ await receiveReport(report_token, "access-to-coop-page-from-opener")
+ assert_equals(report.type, "coop");
+ assert_equals(report.url, openee_url.replace(/"/g, '%22'));
+ assert_equals(report.body.disposition, "reporting");
+ assert_equals(report.body.effectivePolicy, "noopener-allow-popups");
+ assert_equals(report.body.property, "blur");
+ assert_source_location_missing(report);
+ assert_equals(report.body.openerURL, opener_url);
+ assert_equals(report.body.openeeURL, undefined);
+ assert_equals(report.body.otherDocumentURL, undefined);
+ assert_equals(report.body.referrer, opener_url);
+ assert_equals(report.body.initialPopupURL, undefined);
+}, name);
+
+runTest(false, "access-to-coop-page-from-opener, noopener-allow-popups");
+runTest(true , "access-to-coop-page-from-opener, noopener-allow-popups + redirect");
+
+</script>
+
diff --git a/tests/wpt/tests/html/cross-origin-opener-policy/resources/noopener-helper.js b/tests/wpt/tests/html/cross-origin-opener-policy/resources/noopener-helper.js
new file mode 100644
index 00000000000..1f9b0775071
--- /dev/null
+++ b/tests/wpt/tests/html/cross-origin-opener-policy/resources/noopener-helper.js
@@ -0,0 +1,163 @@
+const executor_path = '/common/dispatcher/executor.html?pipe=';
+const coop_header = policy => {
+ return `|header(Cross-Origin-Opener-Policy,${policy})`;
+};
+
+function getExecutorPath(uuid, origin, coop_header) {
+ return origin.origin + executor_path + coop_header + `&uuid=${uuid}`;
+}
+
+// Open a same-origin popup with `opener_coop` header, then open a popup with
+// `openee_coop` header. Check whether the opener can script the openee.
+const test_noopener_opening_popup = (
+ opener_coop,
+ openee_coop,
+ opener_expectation
+) => {
+ promise_test(async t => {
+ // Set up dispatcher communications.
+ const popup_token = token();
+ const reply_token = token();
+ const popup_reply_token = token();
+ const popup_openee_token = token();
+
+ const popup_url = getExecutorPath(
+ popup_token, SAME_ORIGIN, coop_header(opener_coop));
+
+ // We open a popup and then ping it, it will respond after loading.
+ const popup = window.open(popup_url);
+ t.add_cleanup(() => send(popup_token, 'window.close()'));
+ send(popup_token, `send('${reply_token}', 'Popup loaded');`);
+ assert_equals(await receive(reply_token), 'Popup loaded');
+
+ if (opener_coop == 'noopener-allow-popups') {
+ // Assert that we can't script the popup.
+ await t.step_wait(() => popup.closed, 'Opener popup.closed')
+ }
+
+ // Ensure that the popup has no access to its opener.
+ send(popup_token, `
+ let openerDOMAccessAllowed = false;
+ try {
+ openerDOMAccessAllowed = !!self.opener.document.URL;
+ } catch(ex) {}
+ const payload = {
+ opener: !!self.opener,
+ openerDOMAccess: openerDOMAccessAllowed
+ };
+ send('${reply_token}', JSON.stringify(payload));
+ `);
+ let payload = JSON.parse(await receive(reply_token));
+ if (opener_coop == 'noopener-allow-popups') {
+ assert_false(payload.opener, 'popup opener');
+ assert_false(payload.openerDOMAccess, 'popup DOM access');
+ }
+
+ // Open another popup from inside the popup, and wait for it to load.
+ const popup_openee_url = getExecutorPath(popup_openee_token, SAME_ORIGIN,
+ coop_header(openee_coop));
+ send(popup_token, `
+ window.openee = open("${popup_openee_url}");
+ await receive('${popup_reply_token}');
+ const payload = {
+ openee: !!window.openee,
+ closed: window.openee.closed
+ };
+ send('${reply_token}', JSON.stringify(payload));
+ `);
+ t.add_cleanup(() => send(popup_token, 'window.openee.close()'));
+ // Notify the popup that its openee was loaded.
+ send(popup_openee_token, `
+ send('${popup_reply_token}', 'popup openee opened');
+ `);
+
+ // Assert that the popup has access to its openee.
+ payload = JSON.parse(await receive(reply_token));
+ assert_true(payload.openee, 'popup openee');
+
+ // Assert that the openee has access to its popup opener.
+ send(popup_openee_token, `
+ let openerDOMAccessAllowed = false;
+ try {
+ openerDOMAccessAllowed = !!self.opener.document.URL;
+ } catch(ex) {}
+ const payload = {
+ opener: !!self.opener,
+ openerDOMAccess: openerDOMAccessAllowed
+ };
+ send('${reply_token}', JSON.stringify(payload));
+ `);
+ payload = JSON.parse(await receive(reply_token));
+ if (opener_expectation) {
+ assert_true(payload.opener, 'Opener is not null');
+ assert_true(payload.openerDOMAccess, 'No DOM access');
+ } else {
+ assert_false(payload.opener, 'Opener is null');
+ assert_false(payload.openerDOMAccess, 'No DOM access');
+ }
+ },
+ 'noopener-allow-popups ensures that the opener cannot script the openee,' +
+ ' but further popups with no COOP can access their opener: ' +
+ opener_coop + '/' + openee_coop);
+};
+
+// Open a same-origin popup with `popup_coop` header, then navigate away toward
+// one with `noopener-allow-popups`. Check the opener can't script the openee.
+const test_noopener_navigating_away = (popup_coop) => {
+ promise_test(async t => {
+ // Set up dispatcher communications.
+ const popup_token = token();
+ const reply_token = token();
+ const popup_reply_token = token();
+ const popup_second_token = token();
+
+ const popup_url =
+ getExecutorPath(popup_token, SAME_ORIGIN, coop_header(popup_coop));
+
+ // We open a popup and then ping it, it will respond after loading.
+ const popup = window.open(popup_url);
+ send(popup_token, `send('${reply_token}', 'Popup loaded');`);
+ assert_equals(await receive(reply_token), 'Popup loaded');
+ t.add_cleanup(() => send(popup_token, 'window.close()'));
+
+ // Assert that we can script the popup.
+ assert_not_equals(popup.window, null);
+ assert_false(popup.closed, 'popup closed');
+
+ // Ensure that the popup has no access to its opener.
+ send(popup_token, `
+ let openerDOMAccessAllowed = false;
+ try {
+ openerDOMAccessAllowed = !!self.opener.document.URL;
+ } catch(ex) {}
+ const payload = {
+ opener: !!self.opener,
+ openerDOMAccess: openerDOMAccessAllowed
+ };
+ send('${reply_token}', JSON.stringify(payload));
+ `);
+ let payload = JSON.parse(await receive(reply_token));
+ assert_true(payload.opener, 'popup opener');
+ assert_true(payload.openerDOMAccess, 'popup DOM access');
+
+ // Navigate the popup.
+ const popup_second_url = getExecutorPath(
+ popup_second_token, SAME_ORIGIN,
+ coop_header('noopener-allow-popups'));
+
+ send(popup_token, `
+ window.location = '${popup_second_url}';
+ `);
+
+ // Notify the popup that its openee was loaded.
+ send(popup_second_token, `
+ send('${reply_token}', 'popup navigated away');
+ `);
+ assert_equals(await receive(reply_token), 'popup navigated away');
+
+ assert_equals(popup.window, null);
+ assert_true(popup.closed, 'popup.closed');
+ },
+ 'noopener-allow-popups ensures that the opener cannot script the openee,' +
+ ' even after a navigation: ' + popup_coop);
+};
diff --git a/tests/wpt/tests/html/cross-origin-opener-policy/tentative/noopener/coop-noopener-allow-popups-restrict-properties.https.html b/tests/wpt/tests/html/cross-origin-opener-policy/tentative/noopener/coop-noopener-allow-popups-restrict-properties.https.html
new file mode 100644
index 00000000000..3058011c088
--- /dev/null
+++ b/tests/wpt/tests/html/cross-origin-opener-policy/tentative/noopener/coop-noopener-allow-popups-restrict-properties.https.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>
+ Cross-Origin-Opener-Policy: noopener-allow-popups means that the opener
+ has no access to the openee. This test verfies it for restrict-properties
+ COOP values.
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+<script src="../../resources/common.js"></script>
+<script src="../../resources/noopener-helper.js"></script>
+<script>
+
+
+test_noopener_opening_popup("noopener-allow-popups",
+ "restrict-properties",
+ /*opener_expectation=*/true);
+test_noopener_opening_popup("restrict-properties",
+ "noopener-allow-popups",
+ /*opener_expectation=*/false);
+ </script>
diff --git a/tests/wpt/tests/html/cross-origin-opener-policy/tentative/noopener/coop-noopener-allow-popups.https.html b/tests/wpt/tests/html/cross-origin-opener-policy/tentative/noopener/coop-noopener-allow-popups.https.html
new file mode 100644
index 00000000000..0daf3278476
--- /dev/null
+++ b/tests/wpt/tests/html/cross-origin-opener-policy/tentative/noopener/coop-noopener-allow-popups.https.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>
+ Cross-Origin-Opener-Policy: noopener-allow-popups means that the opener
+ has no access to the openee.
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+<script src="../../resources/common.js"></script>
+<script src="../../resources/noopener-helper.js"></script>
+<script>
+
+
+test_noopener_opening_popup("noopener-allow-popups",
+ "unsafe-none",
+ /*opener_expectation=*/true);
+test_noopener_opening_popup("noopener-allow-popups",
+ "noopener-allow-popups",
+ /*opener_expectation=*/false);
+test_noopener_opening_popup("noopener-allow-popups",
+ "same-origin",
+ /*opener_expectation=*/false);
+test_noopener_opening_popup("noopener-allow-popups",
+ "same-origin-allow-popups",
+ /*opener_expectation=*/true);
+test_noopener_opening_popup("same-origin-allow-popups",
+ "noopener-allow-popups",
+ /*opener_expectation=*/false);
+test_noopener_opening_popup("same-origin",
+ "noopener-allow-popups",
+ /*opener_expectation=*/false);
+test_noopener_navigating_away("unsafe-none");
+ </script>
diff --git a/tests/wpt/tests/html/dom/elements/global-attributes/cdata-dir_auto.html b/tests/wpt/tests/html/dom/elements/global-attributes/cdata-dir_auto.html
new file mode 100644
index 00000000000..91231f7e6c6
--- /dev/null
+++ b/tests/wpt/tests/html/dom/elements/global-attributes/cdata-dir_auto.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<head>
+ <meta charset="utf-8">
+ <link rel="author" title="Vincent Hilla" href="mailto:vhilla@mozilla.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/dom.html#the-directionality">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <div id="test1" dir="auto">
+ <![CDATA[foo]]>اختبر
+ </div>
+
+ <iframe src="cdata-iframe.xhtml"></iframe>
+
+ <script>
+ function awaitMessage(msg) {
+ return new Promise(res => {
+ function waitAndRemove(e) {
+ if (e.data != msg)
+ return;
+ window.removeEventListener("message", waitAndRemove);
+ res();
+ }
+ window.addEventListener("message", waitAndRemove);
+ });
+ }
+
+ const subframeLoaded = awaitMessage("subframe-loaded");
+
+ async function createXHTMLCase(id) {
+ await subframeLoaded;
+
+ let iframe = document.querySelector("iframe");
+ iframe.contentWindow.postMessage(id, "*");
+
+ await awaitMessage(id);
+
+ const doc = iframe.contentDocument;
+ const div = doc.getElementById(id);
+ const cdata = div.firstChild;
+
+ return [div, cdata];
+ }
+
+ test(function() {
+ const div = document.getElementById("test1");
+ assert_true(div.matches(":dir(rtl)"));
+ }, "Content of CDATA is ignored for dir=auto in html document");
+
+ promise_test(async function() {
+ let [div, cdata] = await createXHTMLCase(1);
+ assert_true(div.matches(":dir(ltr)"));
+ }, "Text in CDATASection is considered when determining auto directionality");
+
+ promise_test(async function() {
+ let [div, cdata] = await createXHTMLCase(2);
+ assert_true(div.matches(":dir(ltr)"));
+ cdata.remove();
+ assert_true(div.matches(":dir(rtl)"));
+ }, "Directionality is updated when removing CDATASection");
+
+ promise_test(async function() {
+ let [div, cdata] = await createXHTMLCase(3);
+ assert_true(div.matches(":dir(ltr)"));
+ cdata.textContent = "اختبر";
+ assert_true(div.matches(":dir(rtl)"));
+ }, "Directionality is updated when changing text of CDATASection");
+ </script>
+</body>
+</html>
diff --git a/tests/wpt/tests/html/dom/elements/global-attributes/cdata-iframe.xhtml b/tests/wpt/tests/html/dom/elements/global-attributes/cdata-iframe.xhtml
new file mode 100644
index 00000000000..0c48cbef4fc
--- /dev/null
+++ b/tests/wpt/tests/html/dom/elements/global-attributes/cdata-iframe.xhtml
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+ <div id="container"></div>
+ </body>
+
+ <script>
+ function createXHTMLCase(id) {
+ const container = document.getElementById("container");
+
+ const div = document.createElement("div");
+ div.dir = "auto";
+ div.id = id;
+
+ const cdata = document.createCDATASection("foo");
+ const text = document.createTextNode("اختبر");
+ div.appendChild(cdata);
+ div.appendChild(text);
+
+ container.appendChild(div);
+
+ return [div, cdata];
+ }
+
+ window.addEventListener("message", (e) => {
+ createXHTMLCase(e.data);
+ window.top.postMessage(e.data);
+ });
+
+ window.top.postMessage("subframe-loaded");
+ </script>
+</html>
diff --git a/tests/wpt/tests/html/dom/elements/global-attributes/dir-assorted.window.js b/tests/wpt/tests/html/dom/elements/global-attributes/dir-assorted.window.js
index 93f798e6002..3bd63e2ae05 100644
--- a/tests/wpt/tests/html/dom/elements/global-attributes/dir-assorted.window.js
+++ b/tests/wpt/tests/html/dom/elements/global-attributes/dir-assorted.window.js
@@ -97,22 +97,24 @@ for (const tag of ["style", "script", "input", "textarea"]) {
}, `non-html ${tag} element text contents influence dir=auto`);
}
-test(() => {
- const e1 = document.createElement("div");
- e1.dir = "auto";
- const e2 = document.createElement("div");
- e2.dir = "auto";
- e2.innerText = "A";
- e1.append(e2);
- assert_true(e1.matches(":dir(ltr)"), "parent is LTR before changes");
- assert_true(e2.matches(":dir(ltr)"), "child is LTR before changes");
- e2.removeAttribute("dir");
- assert_true(e1.matches(":dir(ltr)"), "parent is LTR after removing dir attribute on child");
- assert_true(e2.matches(":dir(ltr)"), "child is LTR after removing dir attribute on child");
- e2.firstChild.data = "\u05D0";
- assert_false(e1.matches(":dir(ltr)"), "parent is RTL after changing text in child");
- assert_false(e2.matches(":dir(ltr)"), "child is RTL after changing text in child");
-}, "text changes apply to dir=auto on further ancestor after removing dir=auto from closer ancestor");
+for (const dir of ["auto", "ltr"]) {
+ test(() => {
+ const e1 = document.createElement("div");
+ e1.dir = "auto";
+ const e2 = document.createElement("div");
+ e2.dir = dir;
+ e2.innerText = "A";
+ e1.append(e2);
+ assert_true(e1.matches(":dir(ltr)"), "parent is LTR before changes");
+ assert_true(e2.matches(":dir(ltr)"), "child is LTR before changes");
+ e2.removeAttribute("dir");
+ assert_true(e1.matches(":dir(ltr)"), "parent is LTR after removing dir attribute on child");
+ assert_true(e2.matches(":dir(ltr)"), "child is LTR after removing dir attribute on child");
+ e2.firstChild.data = "\u05D0";
+ assert_false(e1.matches(":dir(ltr)"), "parent is RTL after changing text in child");
+ assert_false(e2.matches(":dir(ltr)"), "child is RTL after changing text in child");
+ }, `text changes apply to dir=auto on further ancestor after removing dir=${dir} from closer ancestor`);
+}
for (const bdi_test of [
{ markup: "<bdi dir=ltr>A</bdi>", expected: "ltr", desc: "dir=ltr with LTR contents" },
diff --git a/tests/wpt/tests/html/dom/elements/global-attributes/dir-auto-dynamic-changes.window.js b/tests/wpt/tests/html/dom/elements/global-attributes/dir-auto-dynamic-changes.window.js
index a0cf4aae7ba..d57d8664dfa 100644
--- a/tests/wpt/tests/html/dom/elements/global-attributes/dir-auto-dynamic-changes.window.js
+++ b/tests/wpt/tests/html/dom/elements/global-attributes/dir-auto-dynamic-changes.window.js
@@ -1,18 +1,4 @@
-function setup_tree(light_tree, shadow_tree) {
- let body = document.body;
- let old_length = body.childNodes.length;
- body.insertAdjacentHTML("beforeend", light_tree.trim());
- if (body.childNodes.length != old_length + 1) {
- throw "unexpected markup";
- }
- let result = body.lastChild;
- if (shadow_tree) {
- let shadow = result.querySelector("#root").attachShadow({mode: "open"});
- shadow.innerHTML = shadow_tree.trim();
- return [result, shadow];
- }
- return result;
-}
+// META: script=dir-shadow-utils.js
test(t => {
let a = setup_tree(`
@@ -55,8 +41,7 @@ test(() => {
}, "dir=auto changes for content insertion and removal, in and out of document");
test(() => {
- let tree, shadow;
- [tree, shadow] = setup_tree(`
+ let [tree, shadow] = setup_tree(`
<div>
<div id="root">
<span id="l">A</span>
@@ -88,8 +73,7 @@ test(() => {
}, "dir=auto changes for slot reassignment");
test(() => {
- let tree, shadow;
- [tree, shadow] = setup_tree(`
+ let [tree, shadow] = setup_tree(`
<div dir=auto>
<div id=root>
<div id=text>A</div>
@@ -195,8 +179,7 @@ test(() => {
}, "dynamic changes inside of non-HTML elements");
test(() => {
- let tree, shadow;
- [tree, shadow] = setup_tree(`
+ let [tree, shadow] = setup_tree(`
<div dir="auto">
<div id="root">
<element xmlns="namespace">A</element>
@@ -233,8 +216,7 @@ test(() => {
}, "slotted non-HTML elements");
test(() => {
- let tree, shadow;
- [tree, shadow] = setup_tree(`
+ let [tree, shadow] = setup_tree(`
<div>
<div id="root">
<!-- element goes here -->
@@ -274,3 +256,171 @@ test(() => {
tree.remove();
}, "slotted non-HTML elements after dynamically assigning dir=auto, and dir attribute ignored on non-HTML elements");
+
+test(() => {
+ let e1 = setup_tree(`
+ <div dir=auto>
+ <div dir=ltr>
+ \u05D0
+ </div>
+ </div>
+ `);
+ let e2 = e1.firstElementChild;
+ assert_true(e1.matches(":dir(ltr)"), "parent is LTR before changes");
+ assert_true(e2.matches(":dir(ltr)"), "child is LTR before changes");
+ e2.removeAttribute("dir");
+ assert_false(e1.matches(":dir(ltr)"), "parent is RTL after removing dir from child");
+ assert_false(e2.matches(":dir(ltr)"), "child is RTL after removing dir from child");
+}, "dir=auto ancestor considers text in subtree after removing dir=ltr from it");
+
+test(() => {
+ let tree1, shadow1;
+ [tree1, shadow1] = setup_tree(`
+ <div>
+ <div id="root" dir="auto">
+ <div id="root2">
+ <span>A</span>
+ </div>
+ </div>
+ </div>
+ `,`
+ <slot dir="auto"></slot>
+ `);
+ let tree2 = tree1.querySelector("#root2");
+ let shadow2 = tree2.attachShadow({mode: 'open'});
+ shadow2.innerHTML = '<slot dir="auto"></slot>';
+
+ let slot1 = shadow1.querySelector("slot");
+ let slot2 = shadow2.querySelector("slot");
+ let span = tree1.querySelector("span");
+
+ // span slotted in slot2 hosted in root2 slotted in slot1
+ // span thus impacts auto-dir of two slots
+ assert_true(slot1.matches(":dir(ltr)", "outer slot initially ltr"));
+ assert_true(slot2.matches(":dir(ltr)", "inner slot initially ltr"));
+ span.innerHTML = "\u05D0";
+ assert_true(slot1.matches(":dir(rtl)", "outer slot changed to rtl"));
+ assert_true(slot2.matches(":dir(rtl)", "inner slot changed to rtl"));
+
+ tree1.remove();
+}, 'Slotted content affects multiple dir=auto slots');
+
+test(() => {
+ let [tree, shadow] = setup_tree(`
+ <div>
+ <div id="root">
+ <span>اختبر</span>
+ </div>
+ </div>
+ `, `
+ <slot dir="auto"></slot>
+ `);
+
+ let slot = shadow.querySelector("slot");
+ assert_equals(html_direction(slot), "rtl", "slot initially rtl");
+ let span = tree.querySelector("span");
+ span.remove();
+ assert_equals(html_direction(slot), "ltr", "slot is reset to ltr");
+ tree.remove();
+}, 'Removing slotted content resets direction on dir=auto slot');
+
+test(() => {
+ let [tree, shadow] = setup_tree(`
+ <div>
+ <div id=root>
+ <div>
+ <span>اختبر</span>
+ </div>
+ </div>
+ </div>
+ `,`
+ <slot dir=auto></slot>
+ `);
+
+ let slot = shadow.querySelector("slot");
+ assert_equals(html_direction(slot), "rtl", "slot initially rtl");
+ let span = tree.querySelector("span");
+ span.remove();
+ assert_equals(html_direction(slot), "ltr", "slot is reset to ltr");
+ tree.remove();
+}, 'Removing child of slotted content changes direction on dir=auto slot');
+
+test(() => {
+ let tree;
+ tree = setup_tree(`
+ <div>
+ <span>اختبر</span>
+ <p>Text</p>
+ </div>
+ `);
+ let p = tree.querySelector("p");
+ assert_true(p.matches(":dir(ltr)"), "child initially ltr");
+ tree.dir = "auto";
+ assert_true(p.matches(":dir(rtl)"), "child updated to rtl");
+ tree.remove();
+}, 'Child directionality gets updated when dir=auto is set on parent');
+
+test(() => {
+ let [tree, shadow] = setup_tree(`
+ <div>
+ <div id=root>
+ <input value="اختبر">
+ </div>
+ </div>
+ `,`
+ <slot dir=auto></slot>
+ `);
+ let slot = shadow.querySelector("slot");
+ assert_equals(html_direction(slot), "rtl");
+ let inp = tree.querySelector("input");
+ inp.value = "abc";
+ assert_equals(html_direction(slot), "ltr");
+ tree.remove();
+}, 'dir=auto slot is updated by text in value of input element children');
+
+test(() => {
+ let [tree, shadow] = setup_tree(`
+ <div>
+ <div id=root>
+ <input value="اختبر">
+ abc
+ </div>
+ </div>
+ `,`
+ <slot dir=auto></slot>
+ `);
+ let slot = shadow.querySelector("slot");
+ assert_equals(html_direction(slot), "rtl");
+ let inp = tree.querySelector("input");
+ inp.type = "month";
+ assert_equals(html_direction(slot), "ltr");
+ tree.remove();
+}, 'dir=auto slot is updated if input stops being auto-directionality form-associated');
+
+test(() => {
+ let [tree, shadow] = setup_tree(`
+ <div>
+ <div id=root dir=ltr>
+ <span>اختبر</span>
+ </div>
+ </div>
+ `,`
+ <div dir=auto id=container>
+ <slot></slot>
+ </div>
+ `);
+ let div = shadow.querySelector("#container");
+ let host = tree.querySelector("#root");
+ assert_equals(html_direction(div), 'ltr', 'ltr inherited from host despite rtl content');
+ // set rtl on host directly, test it is propagated through slots
+ host.dir = "rtl";
+ assert_equals(html_direction(div), 'rtl', 'host dir change propagated via slot');
+ host.dir = "";
+ assert_equals(html_direction(host), 'ltr', 'host dir reset to ltr');
+ assert_equals(html_direction(div), 'ltr', 'host dir change propagated via slot');
+ // host inherits rtl from parent, test it is still propagated through slots
+ tree.dir = "rtl";
+ assert_equals(html_direction(host), 'rtl', 'host inherited rtl from parent');
+ assert_equals(html_direction(div), 'rtl', 'host dir change propagated via slot');
+ tree.remove();
+}, 'slot provides updated directionality from host to a dir=auto container');
diff --git a/tests/wpt/tests/html/dom/elements/global-attributes/dir-shadow-utils.js b/tests/wpt/tests/html/dom/elements/global-attributes/dir-shadow-utils.js
index c7d89cf9082..26fa30efe8f 100644
--- a/tests/wpt/tests/html/dom/elements/global-attributes/dir-shadow-utils.js
+++ b/tests/wpt/tests/html/dom/elements/global-attributes/dir-shadow-utils.js
@@ -6,3 +6,19 @@ function html_direction(element) {
}
return is_ltr ? "ltr" : "rtl";
}
+
+function setup_tree(light_tree, shadow_tree) {
+ let body = document.body;
+ let old_length = body.childNodes.length;
+ body.insertAdjacentHTML("beforeend", light_tree.trim());
+ if (body.childNodes.length != old_length + 1) {
+ throw "unexpected markup";
+ }
+ let result = body.lastChild;
+ if (shadow_tree) {
+ let shadow = result.querySelector("#root").attachShadow({mode: "open"});
+ shadow.innerHTML = shadow_tree.trim();
+ return [result, shadow];
+ }
+ return result;
+}
diff --git a/tests/wpt/tests/html/dom/elements/global-attributes/dir-slots-directionality.html b/tests/wpt/tests/html/dom/elements/global-attributes/dir-slots-directionality.html
index db783fc55ca..c0a11ba9a90 100644
--- a/tests/wpt/tests/html/dom/elements/global-attributes/dir-slots-directionality.html
+++ b/tests/wpt/tests/html/dom/elements/global-attributes/dir-slots-directionality.html
@@ -7,91 +7,222 @@
<link rel="help" href="https://github.com/whatwg/html/issues/3699">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
-<div id="host1"><span></span></div>
-<div id="host2" dir="rtl"></div>
-<span id="host3" dir="auto"></span>
-<div id="host4">اختبر</div>
-<div id="host5"></div>
-<div id="host6">اختبر</div>
+<script src="dir-shadow-utils.js"></script>
+
+<div></div>
+
<script>
test(() => {
- let root1 = host1.attachShadow({mode:"open"});
- root1.innerHTML = '<slot dir="rtl"></slot>';
- let span = host1.firstChild;
+ let [tree, shadow] = setup_tree(`
+ <div>
+ <div id="root">
+ <span></span>
+ </div>
+ </div>
+ `,`
+ <slot dir="rtl"></slot>
+ `);
+ let span = tree.querySelector("span");
assert_equals(getComputedStyle(span).direction, "rtl");
- assert_true(span.matches(":dir(ltr)"));
+ assert_equals(html_direction(span), "ltr");
+ tree.remove();
}, 'Slots: Directionality: dir=rtl on slot');
test(() => {
- let root2 = host2.attachShadow({mode:"open"});
- root2.innerHTML = '<span></span>';
- let span = root2.querySelector("span");
+ let [tree, shadow] = setup_tree(`
+ <div>
+ <div id="root" dir="rtl"></div>
+ </div>
+ `,`
+ <span></span>
+ `);
+ let span = shadow.querySelector("span");
assert_equals(getComputedStyle(span).direction, "rtl");
- assert_true(span.matches(":dir(rtl)"));
+ assert_equals(html_direction(span), "rtl");
+ tree.remove();
}, 'Slots: Directionality: dir=rtl on host');
test(() => {
- let root3 = host3.attachShadow({mode:"open"});
- root3.innerHTML = `اختبر`;
- let span = host3;
+ let [tree, shadow] = setup_tree(`
+ <div>
+ <span id="root" dir="auto"></span>
+ </div>
+ `,`
+ اختبر
+ `);
+ let span = tree.querySelector("span");
assert_equals(getComputedStyle(span).direction, "ltr");
- assert_true(span.matches(":dir(ltr)"));
+ assert_equals(html_direction(span), "ltr");
+ tree.remove();
}, 'Slots: Directionality: dir=auto on host with Arabic shadow tree content');
test(() => {
- let root4 = host4.attachShadow({mode:"open"});
- root4.innerHTML = '<span dir="auto"><slot></slot></span>';
- let span = root4.querySelector("span");
+ let [tree, shadow] = setup_tree(`
+ <div>
+ <div id="root">
+ اختبر
+ </div>
+ </div>
+ `,`
+ <span dir="auto">
+ <slot></slot>
+ </span>
+ `);
+ let span = shadow.querySelector("span");
assert_equals(getComputedStyle(span).direction, "ltr");
- assert_true(span.matches(":dir(ltr)"));
+ assert_equals(html_direction(span), "ltr");
+ tree.remove();
}, 'Slots: Directionality: dir=auto in shadow tree with Arabic light tree content');
test(() => {
- let root5 = host5.attachShadow({mode:"open"});
- root5.innerHTML = '<span dir="auto"><slot>اختبر</slot></span>';
- let span = root5.querySelector("span");
+ let [tree, shadow] = setup_tree(`
+ <div>
+ <div id="root"></div>
+ </div>
+ `,`
+ <span dir="auto">
+ <slot>
+ اختبر
+ </slot>
+ </span>
+ `);
+ let span = shadow.querySelector("span");
assert_equals(getComputedStyle(span).direction, "ltr");
- assert_true(span.matches(":dir(ltr)"));
+ assert_equals(html_direction(span), "ltr");
+ tree.remove();
}, 'Slots: Directionality: dir=auto in shadow tree with Arabic shadow tree content');
test(() => {
- let root6 = host6.attachShadow({mode:"open"});
- root6.innerHTML = '<slot dir="auto"></slot>';
- let span = root6.querySelector("slot");
- assert_equals(getComputedStyle(span).direction, "rtl");
- assert_true(span.matches(":dir(rtl)"));
+ let [tree, shadow] = setup_tree(`
+ <div>
+ <div id="root">
+ اختبر
+ </div>
+ </div>
+ `,`
+ <slot dir="auto"></slot>
+ `);
+ let slot = shadow.querySelector("slot");
+ assert_equals(getComputedStyle(slot).direction, "rtl");
+ assert_equals(html_direction(slot), "rtl");
+ tree.remove();
}, 'Slots: Directionality: dir=auto on slot with Arabic light tree content');
test(() => {
- let host = document.createElement("div");
- host.dir = "rtl";
- document.body.appendChild(host);
- let root = host.attachShadow({mode:"open"});
- root.innerHTML = '<section dir="ltr"><div dir="auto"><slot></slot>A</div></section>';
- let div = root.querySelector("div");
- assert_true(div.matches(":dir(rtl)"));
- host.remove();
+ let [tree, shadow] = setup_tree(`
+ <div>
+ <div id="root" dir="rtl"></div>
+ </div>
+ `,`
+ <section dir="ltr">
+ <div dir="auto">
+ <slot></slot>
+ A
+ </div>
+ </section>
+ `);
+ let div = shadow.querySelector("div");
+ assert_equals(html_direction(div), "rtl");
+ tree.remove();
}, 'slot provides its directionality (from host) to a dir=auto container');
test(() => {
- let host = document.createElement("div");
- document.body.appendChild(host);
- let root = host.attachShadow({mode:"open"});
- root.innerHTML = '<div dir="auto"><span dir="ltr">A</span>\u05D0</div><slot></slot>';
- let div = root.querySelector("div");
- assert_true(div.matches(":dir(rtl)"));
- host.remove();
+ let [tree, shadow] = setup_tree(`
+ <div>
+ <div id="root"></div>
+ </div>
+ `,`
+ <div dir="auto">
+ <span dir="ltr">
+ A
+ </span>
+ \u05D0
+ </div>
+ <slot></slot>
+ `);
+ let div = shadow.querySelector("div");
+ assert_equals(html_direction(div), "rtl");
+ tree.remove();
}, 'children with dir attribute are skipped by dir=auto');
test(() => {
- let host = document.createElement("div");
- document.body.appendChild(host);
- let root = host.attachShadow({mode:"open"});
- root.innerHTML = '<div dir="auto"><slot dir="ltr"></slot>\u05D0</div>';
- let div = root.querySelector("div");
- assert_true(div.matches(":dir(rtl)"));
- host.remove();
+ let [tree, shadow] = setup_tree(`
+ <div>
+ <div id="root"></div>
+ </div>
+ `,`
+ <div dir="auto"><slot dir="ltr"></slot>\u05D0</div>
+ `);
+ let div = shadow.querySelector("div");
+ assert_equals(html_direction(div), "rtl");
+ tree.remove();
}, 'slot with dir attribute is skipped by dir=auto');
+test(() => {
+ let [tree, shadow] = setup_tree(`
+ <div>
+ <div id="root" dir="auto">
+ <div dir="ltr">اختبر</div>
+ <span>Text</span>
+ </div>
+ </div>
+ `,`
+ <slot dir="auto"></slot>
+ `);
+ let div = tree.querySelector("#root");
+ assert_equals(html_direction(div), "ltr");
+ let slot = shadow.querySelector("slot");
+ assert_equals(html_direction(slot), "rtl");
+ tree.remove();
+}, 'dir=auto slot ignores dir attribute of assigned nodes');
+
+test(() => {
+ let [tree, shadow] = setup_tree(`
+ <div>
+ <div id="root">
+ <bdi class="name">اختبر</bdi>
+ </div>
+ </div>
+ `, `
+ <slot dir="auto"></slot>
+ `);
+ let slot = shadow.querySelector("slot");
+ assert_equals(html_direction(slot), "rtl");
+ tree.remove();
+}, 'dir=auto slot considers text in bdi assigned nodes');
+
+test(() => {
+ let [tree, shadow] = setup_tree(`
+ <div>
+ <div id=root>
+ <input value="اختبر">
+ </div>
+ </div>
+ `,`
+ <slot dir=auto></slot>
+ `);
+ let inp = tree.querySelector("input");
+ assert_equals(html_direction(inp), "ltr");
+ let slot = shadow.querySelector("slot");
+ assert_equals(html_direction(slot), "rtl");
+ tree.remove();
+
+ [tree, shadow] = setup_tree(`
+ <div>
+ <div id=root>
+ <input value=" ">
+ اختبر
+ </div>
+ </div>
+ `,`
+ <slot dir=auto></slot>
+ `);
+ inp = tree.querySelector("input");
+ assert_equals(html_direction(inp), "ltr");
+ slot = shadow.querySelector("slot");
+ assert_equals(html_direction(slot), "ltr", "no strong character in value counts as ltr");
+ tree.remove();
+}, 'dir=auto slot considers text in value of input element children');
+
</script>
diff --git a/tests/wpt/tests/html/rendering/widgets/input-checkbox-appearance-none-dynamic-crash.html b/tests/wpt/tests/html/rendering/widgets/input-checkbox-appearance-none-dynamic-crash.html
new file mode 100644
index 00000000000..fb10f6a7fa6
--- /dev/null
+++ b/tests/wpt/tests/html/rendering/widgets/input-checkbox-appearance-none-dynamic-crash.html
@@ -0,0 +1,16 @@
+<style>
+input:checked {
+ appearance: none;
+}
+input:required {
+ border-bottom-style: groove;
+}
+</style>
+<script>
+document.addEventListener("DOMContentLoaded", () => {
+ a.getBoundingClientRect();
+ a.defaultChecked = true;
+ a.getBoundingClientRect();
+})
+</script>
+<input id="a" required="required" type="radio">
diff --git a/tests/wpt/tests/html/semantics/forms/constraints/form-validation-reportValidity.html b/tests/wpt/tests/html/semantics/forms/constraints/form-validation-reportValidity.html
index c68e21c9d5f..5f0393f9275 100644
--- a/tests/wpt/tests/html/semantics/forms/constraints/form-validation-reportValidity.html
+++ b/tests/wpt/tests/html/semantics/forms/constraints/form-validation-reportValidity.html
@@ -1,5 +1,6 @@
<!DOCTYPE html>
<meta charset="utf-8">
+<meta name="timeout" content="long">
<title>The constraint validation API Test: element.reportValidity()</title>
<link rel="author" title="Intel" href="http://www.intel.com/">
<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity">
diff --git a/tests/wpt/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-menu-ref.html b/tests/wpt/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-menu-ref.html
index 2b1ea76656f..fce5538f5e0 100644
--- a/tests/wpt/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-menu-ref.html
+++ b/tests/wpt/tests/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-menu-ref.html
@@ -2,9 +2,16 @@
<meta charset="utf-8">
<title>list owner is calculated to be narest ancestor menu if it exists</title>
<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="author" title="Peng Zhou" href="mailto:zhoupeng.1996@bytedance.com">
<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#ordinal-value">
<link rel="help" href="https://html.spec.whatwg.org/multipage/semantics.html#list-owner">
+<style>
+ li {
+ list-style-type: decimal;
+ }
+</style>
+
<p>This test matches if the list displays similar to the following</p>
<pre>1. A
@@ -22,25 +29,25 @@
<hr>
-<ol>
+<menu>
<li value="1">A</li>
<li value="2">B</li>
<li value="3">C</li>
<li value="4">D</li>
<li value="5">E</li>
- <ol>
+ <menu>
<li value="1">F</li>
<li value="2">G</li>
- </ol>
+ </menu>
<li value="6">H</li>
- <ol>
+ <menu>
<li value="1">I</li>
<li value="2">
J
- <ol>
+ <menu>
<li value="1">K</li>
<li value="2">L</li>
- </ol>
+ </menu>
</li>
- </ol>
-</ol>
+ </menu>
+</menu>
diff --git a/tests/wpt/tests/html/semantics/interactive-elements/the-details-element/name-attribute.html b/tests/wpt/tests/html/semantics/interactive-elements/the-details-element/name-attribute.html
index 2685546e9b0..19d62de66f4 100644
--- a/tests/wpt/tests/html/semantics/interactive-elements/the-details-element/name-attribute.html
+++ b/tests/wpt/tests/html/semantics/interactive-elements/the-details-element/name-attribute.html
@@ -194,10 +194,6 @@ promise_test(async t => {
let received_ids = [];
let listener = event => {
received_ids.push(event.target.id);
- let i = 0;
- for (let element of elements) {
- element.setAttribute("name", `b${i++}`);
- }
};
for (let element of elements) {
element.addEventListener("DOMSubtreeModified", listener);
diff --git a/tests/wpt/tests/html/semantics/invokers/idlharness.tentative.html b/tests/wpt/tests/html/semantics/invokers/idlharness.tentative.html
index 8a86a5aaa18..e52ab063f84 100644
--- a/tests/wpt/tests/html/semantics/invokers/idlharness.tentative.html
+++ b/tests/wpt/tests/html/semantics/invokers/idlharness.tentative.html
@@ -10,7 +10,7 @@
<script>
idl_test(["invokers.tentative"], ["html", "dom"], (idl_array) => {
idl_array.add_objects({
- InvokeEvent: ['new InvokeEvent("invoke")'],
+ CommandEvent: ['new CommandEvent("invoke")'],
});
});
</script>
diff --git a/tests/wpt/tests/html/semantics/invokers/invokeelement-interface.tentative.html b/tests/wpt/tests/html/semantics/invokers/invokeelement-interface.tentative.html
index 5adacadabb4..6821adf71fe 100644
--- a/tests/wpt/tests/html/semantics/invokers/invokeelement-interface.tentative.html
+++ b/tests/wpt/tests/html/semantics/invokers/invokeelement-interface.tentative.html
@@ -5,89 +5,84 @@
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
-<button id="invoker" invoketarget="invokee"></button>
+<button id="invoker" commandfor="invokee" command="test"></button>
<div id="invokee"></div>
<script>
test(function () {
- assert_equals(invoker.invokeTargetElement, invokee);
- }, "invokeTargetElement reflects invokee HTML element");
+ assert_equals(invoker.commandForElement, invokee);
+ }, "commandForElement reflects invokee HTML element");
test(function () {
const div = document.body.appendChild(document.createElement("div"));
- invoker.invokeTargetElement = div;
- assert_equals(invoker.invokeTargetElement, div);
- assert_equals(invoker.getAttribute("invoketarget"), "");
- assert_false(invoker.hasAttribute("invokeaction"));
- }, "invokeTargetElement reflects set value");
+ invoker.commandForElement = div;
+ assert_equals(invoker.commandForElement, div);
+ assert_equals(invoker.getAttribute("commandfor"), "");
+ assert_equals(invoker.getAttribute("command"), "test");
+ }, "commandForElement reflects set value");
test(function () {
const host = document.body.appendChild(document.createElement("div"));
const shadow = host.attachShadow({ mode: "open" });
const button = shadow.appendChild(document.createElement("button"));
- button.invokeTargetElement = invokee;
- assert_equals(button.invokeTargetElement, invokee);
- assert_equals(invoker.getAttribute("invoketarget"), "");
- assert_false(invoker.hasAttribute("invokeaction"));
- }, "invokeTargetElement reflects set value across shadow root into light dom");
+ button.commandForElement = invokee;
+ assert_equals(button.commandForElement, invokee);
+ assert_equals(invoker.getAttribute("commandfor"), "");
+ assert_equals(invoker.getAttribute("command"), "test");
+ }, "commandForElement reflects set value across shadow root into light dom");
test(function () {
const host = document.body.appendChild(document.createElement("div"));
const shadow = host.attachShadow({ mode: "open" });
const div = shadow.appendChild(document.createElement("div"));
- invoker.invokeTargetElement = div;
- assert_equals(invoker.invokeTargetElement, null);
- assert_equals(invoker.getAttribute("invoketarget"), "");
- assert_false(invoker.hasAttribute("invokeaction"));
- }, "invokeTargetElement does not reflect set value inside shadowroot");
+ invoker.commandForElement = div;
+ assert_equals(invoker.commandForElement, null);
+ assert_equals(invoker.getAttribute("commandfor"), "");
+ assert_equals(invoker.getAttribute("command"), "test");
+ }, "commandForElement does not reflect set value inside shadowroot");
test(function () {
assert_throws_js(
TypeError,
function () {
- invoker.invokeTargetElement = {};
+ invoker.commandForElement = {};
},
- "invokeTargetElement attribute must be an instance of Element",
+ "commandForElement attribute must be an instance of Element",
);
- }, "invokeTargetElement throws error on assignment of non Element");
+ }, "commandForElement throws error on assignment of non Element");
test(function () {
- assert_false(invoker.hasAttribute("invokeaction"));
- assert_equals(invoker.invokeAction, "");
- }, "invokeAction reflects '' when attribute not present");
+ invoker.setAttribute("command", "");
+ assert_equals(invoker.getAttribute("command"), "");
+ assert_equals(invoker.command, "");
+ }, "command reflects '' when attribute empty, setAttribute version");
test(function () {
- invoker.setAttribute("invokeaction", "");
- assert_equals(invoker.getAttribute("invokeaction"), "");
- assert_equals(invoker.invokeAction, "");
- }, "invokeAction reflects '' when attribute empty, setAttribute version");
+ invoker.command = "fooBarBaz";
+ assert_equals(invoker.getAttribute("command"), "fooBarBaz");
+ assert_equals(invoker.command, "fooBarBaz");
+ }, "command reflects same casing");
test(function () {
- invoker.invokeAction = "fooBarBaz";
- assert_equals(invoker.getAttribute("invokeaction"), "fooBarBaz");
- assert_equals(invoker.invokeAction, "fooBarBaz");
- }, "invokeAction reflects same casing");
+ invoker.command = "";
+ assert_equals(invoker.getAttribute("command"), "");
+ assert_equals(invoker.command, "");
+ }, "command reflects '' when attribute empty, IDL version");
test(function () {
- invoker.invokeAction = "";
- assert_equals(invoker.getAttribute("invokeaction"), "");
- assert_equals(invoker.invokeAction, "");
- }, "invokeAction reflects '' when attribute empty, IDL version");
+ invoker.command = [1, 2, 3];
+ assert_equals(invoker.getAttribute("command"), "1,2,3");
+ assert_equals(invoker.command, "1,2,3");
+ }, "command reflects tostring value");
test(function () {
- invoker.invokeAction = [1, 2, 3];
- assert_equals(invoker.getAttribute("invokeaction"), "1,2,3");
- assert_equals(invoker.invokeAction, "1,2,3");
- }, "invokeAction reflects tostring value");
+ invoker.command = [];
+ assert_equals(invoker.getAttribute("command"), "");
+ assert_equals(invoker.command, "");
+ }, "command reflects '' when attribute set to []");
test(function () {
- invoker.invokeAction = [];
- assert_equals(invoker.getAttribute("invokeaction"), "");
- assert_equals(invoker.invokeAction, "");
- }, "invokeAction reflects '' when attribute set to []");
-
- test(function () {
- invoker.invokeAction = {};
- assert_equals(invoker.invokeAction, "[object Object]");
- }, "invokeAction reflects tostring value 2");
+ invoker.command = {};
+ assert_equals(invoker.command, "[object Object]");
+ }, "command reflects tostring value 2");
</script>
diff --git a/tests/wpt/tests/html/semantics/invokers/invokeevent-dispatch-shadow.tentative.html b/tests/wpt/tests/html/semantics/invokers/invokeevent-dispatch-shadow.tentative.html
index 1ecff887608..fb2a113994f 100644
--- a/tests/wpt/tests/html/semantics/invokers/invokeevent-dispatch-shadow.tentative.html
+++ b/tests/wpt/tests/html/semantics/invokers/invokeevent-dispatch-shadow.tentative.html
@@ -25,7 +25,7 @@
let hostEventTarget = null;
let hostEventInvoker = null;
slot.addEventListener(
- "invoke",
+ "command",
(e) => {
childEvent = e;
childEventTarget = e.target;
@@ -34,7 +34,7 @@
{ once: true },
);
host.addEventListener(
- "invoke",
+ "command",
(e) => {
hostEvent = e;
hostEventTarget = e.target;
@@ -42,13 +42,13 @@
},
{ once: true },
);
- const event = new InvokeEvent("invoke", {
+ const event = new CommandEvent("command", {
bubbles: true,
invoker: slot,
composed: true,
});
slot.dispatchEvent(event);
- assert_true(childEvent instanceof InvokeEvent, "slot saw invoke event");
+ assert_true(childEvent instanceof CommandEvent, "slot saw invoke event");
assert_equals(
childEventTarget,
slot,
@@ -74,7 +74,7 @@
host,
"invoker is retargeted to shadowroot host",
);
- }, "InvokeEvent propagates across shadow boundaries retargeting invoker");
+ }, "CommandEvent propagates across shadow boundaries retargeting invoker");
test(function (t) {
const host = document.createElement("div");
@@ -83,12 +83,13 @@
const shadow = host.attachShadow({ mode: "open" });
const button = shadow.appendChild(document.createElement("button"));
const invokee = host.appendChild(document.createElement("div"));
- button.invokeTargetElement = invokee;
+ button.commandForElement = invokee;
+ button.command = 'test-command';
let event = null;
let eventTarget = null;
let eventInvoker = null;
invokee.addEventListener(
- "invoke",
+ "command",
(e) => {
event = e;
eventTarget = e.target;
@@ -97,8 +98,8 @@
{ once: true },
);
button.click();
- assert_true(event instanceof InvokeEvent);
+ assert_true(event instanceof CommandEvent);
assert_equals(eventTarget, invokee, "target is invokee");
assert_equals(eventInvoker, host, "invoker is host");
- }, "cross shadow InvokeEvent retargets invoker to host element");
+ }, "cross shadow CommandEvent retargets invoker to host element");
</script>
diff --git a/tests/wpt/tests/html/semantics/invokers/invokeevent-interface.tentative.html b/tests/wpt/tests/html/semantics/invokers/invokeevent-interface.tentative.html
index 0cfb4d5ee5f..500c05f88a1 100644
--- a/tests/wpt/tests/html/semantics/invokers/invokeevent-interface.tentative.html
+++ b/tests/wpt/tests/html/semantics/invokers/invokeevent-interface.tentative.html
@@ -14,112 +14,112 @@
<script>
test(function () {
- const event = new InvokeEvent("test");
- assert_equals(event.action, "");
- assert_readonly(event, "action", "readonly attribute value");
- }, "action is a readonly defaulting to ''");
+ const event = new CommandEvent("test");
+ assert_equals(event.command, "");
+ assert_readonly(event, "command", "readonly attribute value");
+ }, "command is a readonly defaulting to ''");
test(function () {
- const event = new InvokeEvent("test");
+ const event = new CommandEvent("test");
assert_equals(event.invoker, null);
assert_readonly(event, "invoker", "readonly attribute value");
}, "invoker is readonly defaulting to null");
test(function () {
- const event = new InvokeEvent("test", { action: "sAmPle" });
- assert_equals(event.action, "sAmPle");
- }, "action reflects initialized attribute");
+ const event = new CommandEvent("test", { command: "sAmPle" });
+ assert_equals(event.command, "sAmPle");
+ }, "command reflects initialized attribute");
test(function () {
- const event = new InvokeEvent("test", { action: undefined });
- assert_equals(event.action, "");
- }, "action set to undefined");
+ const event = new CommandEvent("test", { command: undefined });
+ assert_equals(event.command, "");
+ }, "command set to undefined");
test(function () {
- const event = new InvokeEvent("test", { action: null });
- assert_equals(event.action, "null");
- }, "action set to null");
+ const event = new CommandEvent("test", { command: null });
+ assert_equals(event.command, "null");
+ }, "command set to null");
test(function () {
- const event = new InvokeEvent("test", { action: false });
- assert_equals(event.action, "false");
- }, "action set to false");
+ const event = new CommandEvent("test", { command: false });
+ assert_equals(event.command, "false");
+ }, "command set to false");
test(function () {
- const event = new InvokeEvent("test", { action: "" });
- assert_equals(event.action, "");
- }, "action explicitly set to empty string");
+ const event = new CommandEvent("test", { command: "" });
+ assert_equals(event.command, "");
+ }, "command explicitly set to empty string");
test(function () {
- const event = new InvokeEvent("test", { action: true });
- assert_equals(event.action, "true");
- }, "action set to true");
+ const event = new CommandEvent("test", { command: true });
+ assert_equals(event.command, "true");
+ }, "command set to true");
test(function () {
- const event = new InvokeEvent("test", { action: 0.5 });
- assert_equals(event.action, "0.5");
- }, "action set to a number");
+ const event = new CommandEvent("test", { command: 0.5 });
+ assert_equals(event.command, "0.5");
+ }, "command set to a number");
test(function () {
- const event = new InvokeEvent("test", { action: [] });
- assert_equals(event.action, "");
- }, "action set to []");
+ const event = new CommandEvent("test", { command: [] });
+ assert_equals(event.command, "");
+ }, "command set to []");
test(function () {
- const event = new InvokeEvent("test", { action: [1, 2, 3] });
- assert_equals(event.action, "1,2,3");
- }, "action set to [1, 2, 3]");
+ const event = new CommandEvent("test", { command: [1, 2, 3] });
+ assert_equals(event.command, "1,2,3");
+ }, "command set to [1, 2, 3]");
test(function () {
- const event = new InvokeEvent("test", { action: { sample: 0.5 } });
- assert_equals(event.action, "[object Object]");
- }, "action set to an object");
+ const event = new CommandEvent("test", { command: { sample: 0.5 } });
+ assert_equals(event.command, "[object Object]");
+ }, "command set to an object");
test(function () {
- const event = new InvokeEvent("test", {
- action: {
+ const event = new CommandEvent("test", {
+ command: {
toString() {
return "sample";
},
},
});
- assert_equals(event.action, "sample");
- }, "action set to an object with a toString function");
+ assert_equals(event.command, "sample");
+ }, "command set to an object with a toString function");
test(function () {
- const eventInit = { action: "sample", invoker: document.body };
- const event = new InvokeEvent("test", eventInit);
- assert_equals(event.action, "sample");
+ const eventInit = { command: "sample", invoker: document.body };
+ const event = new CommandEvent("test", eventInit);
+ assert_equals(event.command, "sample");
assert_equals(event.invoker, document.body);
- }, "InvokeEventInit properties set value");
+ }, "CommandEventInit properties set value");
test(function () {
const eventInit = {
- action: "open",
+ command: "open",
invoker: document.getElementById("div"),
};
- const event = new InvokeEvent("beforetoggle", eventInit);
- assert_equals(event.action, "open");
+ const event = new CommandEvent("beforetoggle", eventInit);
+ assert_equals(event.command, "open");
assert_equals(event.invoker, document.getElementById("div"));
- }, "InvokeEventInit properties set value 2");
+ }, "CommandEventInit properties set value 2");
test(function () {
const eventInit = {
- action: "closed",
+ command: "closed",
invoker: document.getElementById("button"),
};
- const event = new InvokeEvent("toggle", eventInit);
- assert_equals(event.action, "closed");
+ const event = new CommandEvent("toggle", eventInit);
+ assert_equals(event.command, "closed");
assert_equals(event.invoker, document.getElementById("button"));
- }, "InvokeEventInit properties set value 3");
+ }, "CommandEventInit properties set value 3");
test(function () {
- const event = new InvokeEvent("test", { invoker: undefined });
+ const event = new CommandEvent("test", { invoker: undefined });
assert_equals(event.invoker, null);
}, "invoker set to undefined");
test(function () {
- const event = new InvokeEvent("test", { invoker: null });
+ const event = new CommandEvent("test", { invoker: null });
assert_equals(event.invoker, null);
}, "invoker set to null");
@@ -127,7 +127,7 @@
assert_throws_js(
TypeError,
function () {
- new InvokeEvent("test", { invoker: false });
+ new CommandEvent("test", { invoker: false });
},
"invoker is not an object",
);
@@ -137,7 +137,7 @@
assert_throws_js(
TypeError,
function () {
- const event = new InvokeEvent("test", { invoker: true });
+ const event = new CommandEvent("test", { invoker: true });
},
"invoker is not an object",
);
@@ -147,7 +147,7 @@
assert_throws_js(
TypeError,
function () {
- const event = new InvokeEvent("test", { invoker: {} });
+ const event = new CommandEvent("test", { invoker: {} });
},
"invoker is not an object",
);
@@ -157,8 +157,8 @@
assert_throws_js(
TypeError,
function () {
- const eventInit = { action: "closed", invoker: new XMLHttpRequest() };
- const event = new InvokeEvent("toggle", eventInit);
+ const eventInit = { command: "closed", invoker: new XMLHttpRequest() };
+ const event = new CommandEvent("toggle", eventInit);
},
"invoker is not an Element",
);
diff --git a/tests/wpt/tests/html/semantics/invokers/invoketarget-button-event-dispatch.tentative.html b/tests/wpt/tests/html/semantics/invokers/invoketarget-button-event-dispatch.tentative.html
index 9120cc31926..c5dfe14f90b 100644
--- a/tests/wpt/tests/html/semantics/invokers/invoketarget-button-event-dispatch.tentative.html
+++ b/tests/wpt/tests/html/semantics/invokers/invoketarget-button-event-dispatch.tentative.html
@@ -11,79 +11,90 @@
<script src="resources/invoker-utils.js"></script>
<div id="invokee"></div>
-<button id="invokerbutton" invoketarget="invokee"></button>
+<button id="invokerbutton" commandfor="invokee" command="custom-command"></button>
+<form id="aform"></form>
<script>
+ aform.addEventListener('submit', (e) => (e.preventDefault()));
+
+ function resetState() {
+ invokerbutton.setAttribute("commandfor", "invokee");
+ invokerbutton.setAttribute("command", "custom-command");
+ invokerbutton.removeAttribute("disabled");
+ invokerbutton.removeAttribute("form");
+ invokerbutton.removeAttribute("type");
+ }
+
promise_test(async function (t) {
let event = null;
- invokee.addEventListener("invoke", (e) => (event = e), { once: true });
+ invokee.addEventListener("command", (e) => (event = e), { once: true });
await clickOn(invokerbutton);
- assert_true(event instanceof InvokeEvent, "event is InvokeEvent");
- assert_equals(event.type, "invoke", "type");
+ assert_true(event instanceof CommandEvent, "event is CommandEvent");
+ assert_equals(event.type, "command", "type");
assert_equals(event.bubbles, false, "bubbles");
assert_equals(event.composed, true, "composed");
assert_equals(event.isTrusted, true, "isTrusted");
- assert_equals(event.action, "", "action");
+ assert_equals(event.command, "custom-command", "command");
assert_equals(event.target, invokee, "target");
assert_equals(event.invoker, invokerbutton, "invoker");
}, "event dispatches on click");
// valid custom invokeactions
["-foo", "foo-", "cAsE-cArRiEs", "-", "-a-", "a-b", "---", "show-picker"].forEach(
- (action) => {
+ (command) => {
promise_test(async function (t) {
- t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ t.add_cleanup(resetState);
let event = null;
- invokee.addEventListener("invoke", (e) => (event = e), { once: true });
- invokerbutton.invokeAction = action;
+ invokee.addEventListener("command", (e) => (event = e), { once: true });
+ invokerbutton.command = command;
await clickOn(invokerbutton);
- assert_true(event instanceof InvokeEvent, "event is InvokeEvent");
- assert_equals(event.type, "invoke", "type");
+ assert_true(event instanceof CommandEvent, "event is CommandEvent");
+ assert_equals(event.type, "command", "type");
assert_equals(event.bubbles, false, "bubbles");
assert_equals(event.composed, true, "composed");
assert_equals(event.isTrusted, true, "isTrusted");
- assert_equals(event.action, action, "action");
+ assert_equals(event.command, command, "command");
assert_equals(event.target, invokee, "target");
assert_equals(event.invoker, invokerbutton, "invoker");
- }, `setting custom invokeAction property to ${action} (must include dash) sets event action`);
+ }, `setting custom command property to ${command} (must include dash) sets event command`);
promise_test(async function (t) {
- t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ t.add_cleanup(resetState);
let event = null;
- invokee.addEventListener("invoke", (e) => (event = e), { once: true });
- invokerbutton.setAttribute("invokeaction", action);
+ invokee.addEventListener("command", (e) => (event = e), { once: true });
+ invokerbutton.setAttribute("command", command);
await clickOn(invokerbutton);
- assert_true(event instanceof InvokeEvent, "event is InvokeEvent");
- assert_equals(event.type, "invoke", "type");
+ assert_true(event instanceof CommandEvent, "event is CommandEvent");
+ assert_equals(event.type, "command", "type");
assert_equals(event.bubbles, false, "bubbles");
assert_equals(event.composed, true, "composed");
assert_equals(event.isTrusted, true, "isTrusted");
- assert_equals(event.action, action, "action");
+ assert_equals(event.command, command, "command");
assert_equals(event.target, invokee, "target");
assert_equals(event.invoker, invokerbutton, "invoker");
- }, `setting custom invokeaction attribute to ${action} (must include dash) sets event action`);
+ }, `setting custom command attribute to ${command} (must include dash) sets event command`);
},
);
// invalid custom invokeactions
- ["foo", "foobar", "foo bar", "em—dash", "hidedocument"].forEach((action) => {
+ ["foo", "foobar", "foo bar", "em—dash", "hidedocument"].forEach((command) => {
promise_test(async function (t) {
- t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ t.add_cleanup(resetState);
let event = null;
- invokee.addEventListener("invoke", (e) => (event = e), { once: true });
- invokerbutton.invokeAction = action;
+ invokee.addEventListener("command", (e) => (event = e), { once: true });
+ invokerbutton.command = command;
await clickOn(invokerbutton);
assert_equals(event, null, "event should not have fired");
- }, `setting custom invokeAction property to ${action} (no dash) did not dispatch an event`);
+ }, `setting custom command property to ${command} (no dash) did not dispatch an event`);
promise_test(async function (t) {
- t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ t.add_cleanup(resetState);
let event = null;
- invokee.addEventListener("invoke", (e) => (event = e), { once: true });
- invokerbutton.setAttribute("invokeaction", action);
+ invokee.addEventListener("command", (e) => (event = e), { once: true });
+ invokerbutton.setAttribute("command", command);
await clickOn(invokerbutton);
assert_equals(event, null, "event should not have fired");
- }, `setting custom invokeaction attribute to ${action} (no dash) did not dispatch an event`);
+ }, `setting custom command attribute to ${command} (no dash) did not dispatch an event`);
});
promise_test(async function (t) {
@@ -96,7 +107,7 @@
{ once: true },
);
invokee.addEventListener(
- "invoke",
+ "command",
(event) => {
called = true;
},
@@ -107,52 +118,49 @@
}, "event does not dispatch if click:preventDefault is called");
promise_test(async function (t) {
- t.add_cleanup(() => invokerbutton.removeAttribute("disabled"));
+ t.add_cleanup(resetState);
let called = false;
- invokee.addEventListener(
- "invoke",
- (event) => {
- called = true;
- },
- { once: true },
- );
+ invokee.addEventListener("command", (e) => (called = true), { once: true });
invokerbutton.setAttribute("disabled", "");
await clickOn(invokerbutton);
assert_false(called, "event was not called");
}, "event does not dispatch if invoker is disabled");
promise_test(async function (t) {
+ t.add_cleanup(resetState);
+ let called = false;
+ invokee.addEventListener("command", (e) => (called = true), { once: true });
+ invokerbutton.setAttribute("form", "aform");
+ await clickOn(invokerbutton);
+ assert_false(called, "event was not called");
+ }, "event does not dispatch if invoker is form associated without `type`");
+
+ promise_test(async function (t) {
+ t.add_cleanup(resetState);
+ let called = false;
+ invokee.addEventListener("command", (e) => (called = true), { once: true });
+ invokerbutton.setAttribute("form", "aform");
+ invokerbutton.setAttribute("type", "button");
+ await clickOn(invokerbutton);
+ assert_true(called, "event was not called");
+ }, "event dispatches if invoker is form associated with `type=button`");
+
+ promise_test(async function (t) {
svgInvokee = document.createElementNS("http://www.w3.org/2000/svg", "svg");
- t.add_cleanup(() => {
- invokerbutton.invokeTargetElement = invokee;
- svgInvokee.remove();
- });
+ svgInvokee.setAttribute("id", "svg-invokee");
+ t.add_cleanup(resetState);
document.body.append(svgInvokee);
- let called = false;
assert_false(svgInvokee instanceof HTMLElement);
assert_true(svgInvokee instanceof Element);
- let eventInvoker = null;
- svgInvokee.addEventListener(
- "invoke",
- (event) => {
- eventInvoker = event.invoker;
- eventTarget = event.target;
- called = true;
- },
- { once: true },
- );
- invokerbutton.invokeTargetElement = svgInvokee;
+ let event = null;
+ svgInvokee.addEventListener("command", (e) => (event = e), { once: true });
+ invokerbutton.setAttribute("commandfor", "svg-invokee");
+ invokerbutton.setAttribute("command", "custom-command");
+ assert_equals(invokerbutton.commandForElement, svgInvokee);
await clickOn(invokerbutton);
- assert_true(called, "event was called");
- assert_equals(
- eventInvoker,
- invokerbutton,
- "event.invoker is set to right element",
- );
- assert_equals(
- eventTarget,
- svgInvokee,
- "event.target is set to right element",
- );
+ assert_not_equals(event, null, "event was called");
+ assert_true(event instanceof CommandEvent, "event is CommandEvent");
+ assert_equals(event.invoker, invokerbutton, "event.invoker is set to right element");
+ assert_equals(event.target, svgInvokee, "event.target is set to right element");
}, "event dispatches if invokee is non-HTML Element");
</script>
diff --git a/tests/wpt/tests/html/semantics/invokers/invoketarget-fullscreen-behavior.tentative.html b/tests/wpt/tests/html/semantics/invokers/invoketarget-fullscreen-behavior.tentative.html
index 2e2c5f683f0..1210b8637de 100644
--- a/tests/wpt/tests/html/semantics/invokers/invoketarget-fullscreen-behavior.tentative.html
+++ b/tests/wpt/tests/html/semantics/invokers/invoketarget-fullscreen-behavior.tentative.html
@@ -12,65 +12,47 @@
<div id="invokee">
Fullscreen content
- <button id="invokerbutton" invoketarget="invokee"></button>
+ <button id="invokerbutton" commandfor="invokee"></button>
</div>
<script>
- // auto
-
- promise_test(async function (t) {
- t.add_cleanup(async () => {
- if (document.fullscreenElement) await document.exitFullscreen();
- });
- assert_false(invokee.matches(":fullscreen"));
- await clickOn(invokerbutton);
- assert_false(invokee.matches(":fullscreen"));
- }, "invoking div with auto action is no-op");
+ async function resetState() {
+ invokerbutton.setAttribute("command", "toggleFullscreen");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ }
// toggleFullscreen
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- if (document.fullscreenElement) await document.exitFullscreen();
- });
+ t.add_cleanup(resetState);
assert_false(invokee.matches(":fullscreen"));
- invokerbutton.setAttribute("invokeaction", "toggleFullscreen");
+ invokerbutton.setAttribute("command", "toggleFullscreen");
await clickOn(invokerbutton);
assert_true(invokee.matches(":fullscreen"));
}, "invoking div with toggleFullscreen action makes div fullscreen");
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- if (document.fullscreenElement) await document.exitFullscreen();
- });
+ t.add_cleanup(resetState);
assert_false(invokee.matches(":fullscreen"));
- invokerbutton.setAttribute("invokeaction", "toggleFullscreen");
+ invokerbutton.setAttribute("command", "toggleFullscreen");
invokerbutton.click();
assert_false(invokee.matches(":fullscreen"));
}, "invoking div with toggleFullscreen action (without user activation) is a no-op");
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- if (document.fullscreenElement) await document.exitFullscreen();
- });
- invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ t.add_cleanup(resetState);
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
assert_false(invokee.matches(":fullscreen"));
- invokerbutton.setAttribute("invokeaction", "toggleFullscreen");
+ invokerbutton.setAttribute("command", "toggleFullscreen");
await clickOn(invokerbutton);
assert_false(invokee.matches(":fullscreen"));
}, "invoking div with toggleFullscreen action and preventDefault is a no-op");
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- if (document.fullscreenElement) await document.exitFullscreen();
- });
- invokerbutton.setAttribute("invokeaction", "toggleFullscreen");
+ t.add_cleanup(resetState);
+ invokerbutton.setAttribute("command", "toggleFullscreen");
await test_driver.bless('go fullscreen');
await invokee.requestFullscreen();
assert_true(invokee.matches(":fullscreen"));
@@ -79,11 +61,8 @@
}, "invoking fullscreen div with toggleFullscreen action exits fullscreen");
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- if (document.fullscreenElement) await document.exitFullscreen();
- });
- invokerbutton.setAttribute("invokeaction", "tOgGlEFullscreen");
+ t.add_cleanup(resetState);
+ invokerbutton.setAttribute("command", "tOgGlEFullscreen");
await test_driver.bless('go fullscreen');
await invokee.requestFullscreen();
assert_true(invokee.matches(":fullscreen"));
@@ -94,36 +73,27 @@
// requestFullscreen
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- if (document.fullscreenElement) await document.exitFullscreen();
- });
+ t.add_cleanup(resetState);
assert_false(invokee.matches(":fullscreen"));
- invokerbutton.setAttribute("invokeaction", "requestFullscreen");
+ invokerbutton.setAttribute("command", "requestFullscreen");
await clickOn(invokerbutton);
assert_true(invokee.matches(":fullscreen"));
}, "invoking div with requestFullscreen action makes div fullscreen");
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- if (document.fullscreenElement) await document.exitFullscreen();
- });
- invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ t.add_cleanup(resetState);
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
assert_false(invokee.matches(":fullscreen"));
- invokerbutton.setAttribute("invokeaction", "requestFullscreen");
+ invokerbutton.setAttribute("command", "requestFullscreen");
await clickOn(invokerbutton);
assert_false(invokee.matches(":fullscreen"));
}, "invoking div with requestFullscreen action and preventDefault is a no-op");
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- if (document.fullscreenElement) await document.exitFullscreen();
- });
- invokerbutton.setAttribute("invokeaction", "requestFullscreen");
+ t.add_cleanup(resetState);
+ invokerbutton.setAttribute("command", "requestFullscreen");
await test_driver.bless('go fullscreen');
await invokee.requestFullscreen();
assert_true(invokee.matches(":fullscreen"));
@@ -134,22 +104,16 @@
// exitFullscreen
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- if (document.fullscreenElement) await document.exitFullscreen();
- });
+ t.add_cleanup(resetState);
assert_false(invokee.matches(":fullscreen"));
- invokerbutton.setAttribute("invokeaction", "exitFullscreen");
+ invokerbutton.setAttribute("command", "exitFullscreen");
await clickOn(invokerbutton);
assert_false(invokee.matches(":fullscreen"));
}, "invoking div with exitFullscreen action is a no-op");
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- if (document.fullscreenElement) await document.exitFullscreen();
- });
- invokerbutton.setAttribute("invokeaction", "exitFullscreen");
+ t.add_cleanup(resetState);
+ invokerbutton.setAttribute("command", "exitFullscreen");
await test_driver.bless('go fullscreen');
await invokee.requestFullscreen();
assert_true(invokee.matches(":fullscreen"));
@@ -158,14 +122,11 @@
}, "invoking fullscreen div with exitFullscreen action exits fullscreen");
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- if (document.fullscreenElement) await document.exitFullscreen();
- });
- invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ t.add_cleanup(resetState);
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
- invokerbutton.setAttribute("invokeaction", "exitFullscreen");
+ invokerbutton.setAttribute("command", "exitFullscreen");
await test_driver.bless('go fullscreen');
await invokee.requestFullscreen();
assert_true(invokee.matches(":fullscreen"));
diff --git a/tests/wpt/tests/html/semantics/invokers/invoketarget-on-audio-behavior.tentative.html b/tests/wpt/tests/html/semantics/invokers/invoketarget-on-audio-behavior.tentative.html
index 37acb7a5396..309a91e2842 100644
--- a/tests/wpt/tests/html/semantics/invokers/invoketarget-on-audio-behavior.tentative.html
+++ b/tests/wpt/tests/html/semantics/invokers/invoketarget-on-audio-behavior.tentative.html
@@ -11,38 +11,22 @@
<script src="resources/invoker-utils.js"></script>
<audio controls id="invokee" src="/media/sound_5.mp3"></audio>
-<button id="invokerbutton" invoketarget="invokee"></button>
+<button id="invokerbutton" commandfor="invokee" command="mute"></button>
<script>
- // auto
-
- promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- invokee.pause();
- invokee.currentTime = 0;
- invokee.muted = false;
- });
- assert_true(invokee.paused);
- invokerbutton.setAttribute("invokeaction", "");
- await clickOn(invokerbutton);
- await new Promise((resolve) => {
- requestAnimationFrame(resolve);
- });
- assert_true(invokee.paused);
- }, "invoking audio with auto action is no-op");
+ function resetState() {
+ invokerbutton.setAttribute("command", "mute");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ }
// playpause
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- invokee.pause();
- invokee.currentTime = 0;
- invokee.muted = false;
- });
+ t.add_cleanup(resetState);
assert_true(invokee.paused);
- invokerbutton.setAttribute("invokeaction", "playpause");
+ invokerbutton.setAttribute("command", "playpause");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -51,14 +35,9 @@
}, "invoking audio with playpause action makes audio play");
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- invokee.pause();
- invokee.currentTime = 0;
- invokee.muted = false;
- });
+ t.add_cleanup(resetState);
assert_true(invokee.paused);
- invokerbutton.setAttribute("invokeaction", "playpause");
+ invokerbutton.setAttribute("command", "playpause");
invokerbutton.click();
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -67,17 +46,12 @@
}, "invoking audio with playpause action (without user activation) is a no-op");
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- invokee.pause();
- invokee.currentTime = 0;
- invokee.muted = false;
- });
- invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ t.add_cleanup(resetState);
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
assert_true(invokee.paused);
- invokerbutton.setAttribute("invokeaction", "playpause");
+ invokerbutton.setAttribute("command", "playpause");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -86,16 +60,11 @@
}, "invoking audio with playpause action and preventDefault is a no-op");
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- invokee.pause();
- invokee.currentTime = 0;
- invokee.muted = false;
- });
+ t.add_cleanup(resetState);
await test_driver.bless("play audio");
invokee.play();
assert_false(invokee.paused);
- invokerbutton.setAttribute("invokeaction", "playpause");
+ invokerbutton.setAttribute("command", "playpause");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -106,14 +75,9 @@
// play
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- invokee.pause();
- invokee.currentTime = 0;
- invokee.muted = false;
- });
+ t.add_cleanup(resetState);
assert_true(invokee.paused);
- invokerbutton.setAttribute("invokeaction", "play");
+ invokerbutton.setAttribute("command", "play");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -122,14 +86,9 @@
}, "invoking audio with play action makes audio play");
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- invokee.pause();
- invokee.currentTime = 0;
- invokee.muted = false;
- });
+ t.add_cleanup(resetState);
assert_true(invokee.paused);
- invokerbutton.setAttribute("invokeaction", "play");
+ invokerbutton.setAttribute("command", "play");
invokerbutton.click();
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -138,17 +97,12 @@
}, "invoking audio with play action (without user activation) is a no-op");
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- invokee.pause();
- invokee.currentTime = 0;
- invokee.muted = false;
- });
- invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ t.add_cleanup(resetState);
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
assert_true(invokee.paused);
- invokerbutton.setAttribute("invokeaction", "play");
+ invokerbutton.setAttribute("command", "play");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -157,16 +111,11 @@
}, "invoking audio with play action and preventDefault is a no-op");
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- invokee.pause();
- invokee.currentTime = 0;
- invokee.muted = false;
- });
+ t.add_cleanup(resetState);
await test_driver.bless("play audio");
invokee.play();
assert_false(invokee.paused);
- invokerbutton.setAttribute("invokeaction", "play");
+ invokerbutton.setAttribute("command", "play");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -177,14 +126,9 @@
// pause
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- invokee.pause();
- invokee.currentTime = 0;
- invokee.muted = false;
- });
+ t.add_cleanup(resetState);
assert_true(invokee.paused);
- invokerbutton.setAttribute("invokeaction", "pause");
+ invokerbutton.setAttribute("command", "pause");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -193,17 +137,12 @@
}, "invoking audio with pause action is a no-op");
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- invokee.pause();
- invokee.currentTime = 0;
- invokee.muted = false;
- });
- invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ t.add_cleanup(resetState);
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
assert_true(invokee.paused);
- invokerbutton.setAttribute("invokeaction", "pause");
+ invokerbutton.setAttribute("command", "pause");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -212,16 +151,11 @@
}, "invoking audio with pause action and preventDefault is a no-op");
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- invokee.pause();
- invokee.currentTime = 0;
- invokee.muted = false;
- });
+ t.add_cleanup(resetState);
await test_driver.bless("play audio");
invokee.play();
assert_false(invokee.paused);
- invokerbutton.setAttribute("invokeaction", "pause");
+ invokerbutton.setAttribute("command", "pause");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -232,14 +166,9 @@
// mute
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- invokee.pause();
- invokee.currentTime = 0;
- invokee.muted = false;
- });
+ t.add_cleanup(resetState);
assert_false(invokee.muted);
- invokerbutton.setAttribute("invokeaction", "toggleMuted");
+ invokerbutton.setAttribute("command", "toggleMuted");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -248,17 +177,12 @@
}, "invoking audio with toggleMuted action mutes it");
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- invokee.pause();
- invokee.currentTime = 0;
- invokee.muted = false;
- });
- invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ t.add_cleanup(resetState);
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
assert_false(invokee.muted);
- invokerbutton.setAttribute("invokeaction", "toggleMuted");
+ invokerbutton.setAttribute("command", "toggleMuted");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -267,15 +191,10 @@
}, "invoking audio with toggleMuted action and preventDefault is a no-op");
promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- invokee.pause();
- invokee.currentTime = 0;
- invokee.muted = false;
- });
+ t.add_cleanup(resetState);
invokee.muted = true;
assert_true(invokee.muted);
- invokerbutton.setAttribute("invokeaction", "toggleMuted");
+ invokerbutton.setAttribute("command", "toggleMuted");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
diff --git a/tests/wpt/tests/html/semantics/invokers/invoketarget-on-audio-invalid-behavior.tentative.html b/tests/wpt/tests/html/semantics/invokers/invoketarget-on-audio-invalid-behavior.tentative.html
index 9e15ce38e85..3e18478a52f 100644
--- a/tests/wpt/tests/html/semantics/invokers/invoketarget-on-audio-invalid-behavior.tentative.html
+++ b/tests/wpt/tests/html/semantics/invokers/invoketarget-on-audio-invalid-behavior.tentative.html
@@ -11,11 +11,12 @@
<script src="resources/invoker-utils.js"></script>
<audio controls id="invokee" src="/media/sound_5.mp3"></audio>
-<button id="invokerbutton" invoketarget="invokee"></button>
+<button id="invokerbutton" commandfor="invokee"></button>
<script>
// invalid actions on audio
[
+ "",
"foo-bar",
"showpopover",
"showmodal",
@@ -24,8 +25,8 @@
"close",
].forEach((action) => {
promise_test(async function (t) {
- t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
- invokerbutton.setAttribute("invokeaction", action);
+ t.add_cleanup(() => invokerbutton.removeAttribute("command"));
+ invokerbutton.setAttribute("command", action);
assert_true(invokee.paused);
assert_false(invokee.muted);
await clickOn(invokerbutton);
diff --git a/tests/wpt/tests/html/semantics/invokers/invoketarget-on-details-behavior.tentative.html b/tests/wpt/tests/html/semantics/invokers/invoketarget-on-details-behavior.tentative.html
index ad9b6caa57d..4c4998cf9d8 100644
--- a/tests/wpt/tests/html/semantics/invokers/invoketarget-on-details-behavior.tentative.html
+++ b/tests/wpt/tests/html/semantics/invokers/invoketarget-on-details-behavior.tentative.html
@@ -11,95 +11,83 @@
<script src="resources/invoker-utils.js"></script>
<details id="invokee">Details Contents</details>
-<button id="invokerbutton" invoketarget="invokee"></button>
+<button id="invokerbutton" commandfor="invokee" command="open"></button>
<script>
function resetState() {
- invokerbutton.removeAttribute("invokeaction");
+ invokerbutton.removeAttribute("command");
invokee.removeAttribute("open");
}
// Open actions
[
- null,
- "",
"toggle",
"open",
/* test case sensitivity */
"tOgGlE",
"oPeN",
- ].forEach((action) => {
+ ].forEach((command) => {
promise_test(
async function (t) {
t.add_cleanup(resetState);
- if (action !== null) invokerbutton.invokeAction = action;
+ invokerbutton.command = command;
assert_false(invokee.matches("[open]"));
await clickOn(invokerbutton);
assert_true(invokee.matches("[open]"));
},
- `invoking (as ${
- action === null ? "auto" : action || "explicit empty"
- }) closed details opens`,
+ `invoking (as ${command}) closed details opens`,
);
promise_test(
async function (t) {
t.add_cleanup(resetState);
- if (action !== null) invokerbutton.invokeAction = action;
+ invokerbutton.command = command;
assert_false(invokee.matches("[open]"));
- invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
await clickOn(invokerbutton);
t.add_cleanup(() => invokee.removeAttribute("open"));
assert_false(invokee.matches("[open]"));
},
- `invoking (as ${
- action === null ? "auto" : action || "explicit empty"
- }) closed details with preventDefault does not open`,
+ `invoking (as ${command}) closed details with preventDefault does not open`,
);
});
// Close actions
[
- null,
- "",
"toggle",
"close",
/* test case sensitivity */
"tOgGlE",
"cLoSe",
- ].forEach((action) => {
+ ].forEach((command) => {
promise_test(
async function (t) {
t.add_cleanup(resetState);
- if (action !== null) invokerbutton.invokeAction = action;
+ invokerbutton.command = command;
invokee.setAttribute("open", "");
assert_true(invokee.matches("[open]"));
await clickOn(invokerbutton);
assert_false(invokee.matches("[open]"));
},
- `invoking (as ${
- action === null ? "auto" : action || "explicit empty"
- }) open details closes`,
+ `invoking (as ${command}) open details closes`,
);
promise_test(
async function (t) {
t.add_cleanup(resetState);
- if (action !== null) invokerbutton.invokeAction = action;
+ invokerbutton.command = command;
invokee.setAttribute("open", "");
- invokerbutton.setAttribute("invokeaction", "toggle");
- invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ invokerbutton.setAttribute("command", "toggle");
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
assert_true(invokee.matches("[open]"));
await clickOn(invokerbutton);
assert_true(invokee.matches("[open]"));
},
- `invoking (as ${
- action === null ? "auto" : action || "explicit empty"
- }) open details with prevent default closes`,
+ `invoking (as ${command}) open details with prevent default closes`,
);
});
@@ -107,9 +95,9 @@
promise_test(async function (t) {
t.add_cleanup(resetState);
- invokerbutton.invokeAction = "toggle";
+ invokerbutton.command = "toggle";
invokee.addEventListener(
- "invoke",
+ "command",
(e) => {
invokee.setAttribute("open", "");
},
@@ -126,20 +114,20 @@
promise_test(async function (t) {
t.add_cleanup(resetState);
- invokerbutton.invokeAction = "open";
+ invokerbutton.command = "open";
invokee.setAttribute("open", "");
assert_true(invokee.matches("[open]"));
await clickOn(invokerbutton);
assert_true(invokee.matches("[open]"));
- }, "invoking open details with open action is noop");
+ }, "invoking open details with open command is noop");
// close
promise_test(async function (t) {
t.add_cleanup(resetState);
- invokerbutton.invokeAction = "close";
+ invokerbutton.command = "close";
assert_false(invokee.matches("[open]"));
await clickOn(invokerbutton);
assert_false(invokee.matches("[open]"));
- }, "invoking closed details with close action is noop");
+ }, "invoking closed details with close command is noop");
</script>
diff --git a/tests/wpt/tests/html/semantics/invokers/invoketarget-on-details-invalid-behavior.tentative.html b/tests/wpt/tests/html/semantics/invokers/invoketarget-on-details-invalid-behavior.tentative.html
index d5e90af9c0e..3a4e86e9f2c 100644
--- a/tests/wpt/tests/html/semantics/invokers/invoketarget-on-details-invalid-behavior.tentative.html
+++ b/tests/wpt/tests/html/semantics/invokers/invoketarget-on-details-invalid-behavior.tentative.html
@@ -11,16 +11,17 @@
<script src="resources/invoker-utils.js"></script>
<details id="invokee">Details Contents</details>
-<button id="invokerbutton" invoketarget="invokee"></button>
+<button id="invokerbutton" commandfor="invokee" command="open"></button>
<script>
function resetState() {
- invokerbutton.removeAttribute("invokeaction");
+ invokerbutton.removeAttribute("command");
invokee.removeAttribute("open");
}
// invalid actions on details
[
+ "",
"foo-bar",
"showpopover",
"showmodal",
@@ -28,22 +29,22 @@
"hidepopover",
"hide",
"toggleopen",
- ].forEach((action) => {
+ ].forEach((command) => {
promise_test(async function (t) {
t.add_cleanup(resetState);
- invokerbutton.invokeAction = action;
+ invokerbutton.command = command;
assert_false(invokee.matches("[open]"));
await clickOn(invokerbutton);
assert_false(invokee.matches("[open]"));
- }, `invoking (as ${action}) on details does nothing`);
+ }, `invoking (as ${command}) on details does nothing`);
promise_test(async function (t) {
t.add_cleanup(resetState);
- invokerbutton.invokeAction = action;
+ invokerbutton.command = command;
invokee.setAttribute("open", "");
assert_true(invokee.matches("[open]"));
await clickOn(invokerbutton);
assert_true(invokee.matches("[open]"));
- }, `invoking (as ${action}) on open details does nothing`);
+ }, `invoking (as ${command}) on open details does nothing`);
});
</script>
diff --git a/tests/wpt/tests/html/semantics/invokers/invoketarget-on-dialog-behavior.tentative.html b/tests/wpt/tests/html/semantics/invokers/invoketarget-on-dialog-behavior.tentative.html
index 9f73e092b03..9cf1e530b3d 100644
--- a/tests/wpt/tests/html/semantics/invokers/invoketarget-on-dialog-behavior.tentative.html
+++ b/tests/wpt/tests/html/semantics/invokers/invoketarget-on-dialog-behavior.tentative.html
@@ -11,43 +11,39 @@
<script src="resources/invoker-utils.js"></script>
<dialog id="invokee">
- <button id="containedinvoker" invoketarget="invokee"></button>
+ <button id="containedinvoker" commandfor="invokee" command="close"></button>
</dialog>
-<button id="invokerbutton" invoketarget="invokee"></button>
+<button id="invokerbutton" commandfor="invokee" command="showmodal"></button>
<script>
function resetState() {
invokee.close();
try { invokee.hidePopover(); } catch {}
invokee.removeAttribute("popover");
- invokerbutton.removeAttribute("invokeaction");
- containedinvoker.removeAttribute("invokeaction");
+ invokerbutton.setAttribute("command", "showmodal");
+ containedinvoker.setAttribute("command", "close");
}
// opening a dialog
- [null, "", "showmodal", /* test case sensitivity */ "sHoWmOdAl"].forEach(
- (action) => {
+ ["showmodal", /* test case sensitivity */ "sHoWmOdAl"].forEach(
+ (command) => {
["property", "attribute"].forEach((setType) => {
promise_test(
async function (t) {
t.add_cleanup(resetState);
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
- if (typeof action === "string") {
- if (setType === "property") {
- invokerbutton.invokeaction = action;
- } else {
- invokerbutton.setAttribute("invokeaction", action);
- }
+ if (setType === "property") {
+ invokerbutton.command = command;
+ } else {
+ invokerbutton.setAttribute("command", command);
}
await clickOn(invokerbutton);
assert_true(invokee.open, "invokee.open");
assert_true(invokee.matches(":modal"), "invokee :modal");
},
- `invoking (with invokeaction ${setType} as ${
- action == null ? "auto" : action || "explicit empty"
- }) closed dialog opens as modal`,
+ `invoking (with command ${setType} as ${command}) closed dialog opens as modal`,
);
promise_test(
@@ -55,23 +51,19 @@
t.add_cleanup(resetState);
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
- invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
- if (typeof action === "string") {
- if (setType === "property") {
- invokerbutton.invokeaction = action;
- } else {
- invokerbutton.setAttribute("invokeaction", action);
- }
+ if (setType === "property") {
+ invokerbutton.command = command;
+ } else {
+ invokerbutton.setAttribute("command", command);
}
await clickOn(invokerbutton);
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
},
- `invoking (with invokeaction ${setType} as ${
- action == null ? "auto" : action || "explicit empty"
- }) closed dialog with preventDefault is noop`,
+ `invoking (with command ${setType} as ${command}) closed dialog with preventDefault is noop`,
);
promise_test(
@@ -80,26 +72,22 @@
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
invokee.addEventListener(
- "invoke",
+ "command",
(e) => {
- invokerbutton.setAttribute("invokeaction", "close");
+ invokerbutton.setAttribute("command", "close");
},
{ once: true },
);
- if (typeof action === "string") {
- if (setType === "property") {
- invokerbutton.invokeaction = action;
- } else {
- invokerbutton.setAttribute("invokeaction", action);
- }
+ if (setType === "property") {
+ invokerbutton.command = command;
+ } else {
+ invokerbutton.setAttribute("command", command);
}
await clickOn(invokerbutton);
assert_true(invokee.open, "invokee.open");
assert_true(invokee.matches(":modal"), "invokee :modal");
},
- `invoking (with invokeaction ${setType} as ${
- action == null ? "auto" : action || "explicit empty"
- }) while changing action still opens as modal`,
+ `invoking (with command ${setType} as ${command}) while changing command still opens as modal`,
);
});
},
@@ -107,7 +95,7 @@
// closing an already open dialog
- [null, "", "close", /* test case sensitivity */ "cLoSe"].forEach((action) => {
+ ["close", /* test case sensitivity */ "cLoSe"].forEach((command) => {
["property", "attribute"].forEach((setType) => {
promise_test(
async function (t) {
@@ -115,20 +103,16 @@
invokee.show();
assert_true(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
- if (typeof action === "string") {
- if (setType === "property") {
- containedinvoker.invokeaction = action;
- } else {
- containedinvoker.setAttribute("invokeaction", action);
- }
+ if (setType === "property") {
+ containedinvoker.command = command;
+ } else {
+ containedinvoker.setAttribute("command", command);
}
await clickOn(containedinvoker);
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
},
- `invoking to close (with invokeaction ${setType} as ${
- action == null ? "auto" : action || "explicit empty"
- }) open dialog closes`,
+ `invoking to close (with command ${setType} as ${command}) open dialog closes`,
);
promise_test(
@@ -137,23 +121,21 @@
invokee.show();
assert_true(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
- if (typeof action === "string") {
+ if (typeof command === "string") {
if (setType === "property") {
- containedinvoker.invokeaction = action;
+ containedinvoker.command = command;
} else {
- containedinvoker.setAttribute("invokeaction", action);
+ containedinvoker.setAttribute("command", command);
}
}
- invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
await clickOn(containedinvoker);
assert_true(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
},
- `invoking to close (with invokeaction ${setType} as ${
- action == null ? "auto" : action || "explicit empty"
- }) open dialog with preventDefault is no-op`,
+ `invoking to close (with command ${setType} as ${command}) open dialog with preventDefault is no-op`,
);
promise_test(
@@ -162,23 +144,19 @@
invokee.showModal();
assert_true(invokee.open, "invokee.open");
assert_true(invokee.matches(":modal"), "invokee :modal");
- if (typeof action === "string") {
- if (setType === "property") {
- containedinvoker.invokeaction = action;
- } else {
- containedinvoker.setAttribute("invokeaction", action);
- }
+ if (setType === "property") {
+ containedinvoker.command = command;
+ } else {
+ containedinvoker.setAttribute("command", command);
}
- invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
await clickOn(containedinvoker);
assert_true(invokee.open, "invokee.open");
assert_true(invokee.matches(":modal"), "invokee :modal");
},
- `invoking to close (with invokeaction ${setType} as ${
- action == null ? "auto" : action || "explicit empty"
- }) open modal dialog with preventDefault is no-op`,
+ `invoking to close (with command ${setType} as ${command}) open modal dialog with preventDefault is no-op`,
);
promise_test(
@@ -187,17 +165,15 @@
invokee.show();
assert_true(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
- if (typeof action === "string") {
- if (setType === "property") {
- containedinvoker.invokeaction = action;
- } else {
- containedinvoker.setAttribute("invokeaction", action);
- }
+ if (setType === "property") {
+ containedinvoker.command = command;
+ } else {
+ containedinvoker.setAttribute("command", command);
}
invokee.addEventListener(
- "invoke",
+ "command",
(e) => {
- containedinvoker.setAttribute("invokeaction", "show");
+ containedinvoker.setAttribute("command", "show");
},
{ once: true },
);
@@ -205,9 +181,7 @@
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
},
- `invoking to close (with invokeaction ${setType} as ${
- action == null ? "auto" : action || "explicit empty"
- }) open dialog while changing action still closes`,
+ `invoking to close (with command ${setType} as ${command}) open dialog while changing command still closes`,
);
promise_test(
@@ -216,17 +190,15 @@
invokee.showModal();
assert_true(invokee.open, "invokee.open");
assert_true(invokee.matches(":modal"), "invokee :modal");
- if (typeof action === "string") {
- if (setType === "property") {
- containedinvoker.invokeaction = action;
- } else {
- containedinvoker.setAttribute("invokeaction", action);
- }
+ if (setType === "property") {
+ containedinvoker.command = command;
+ } else {
+ containedinvoker.setAttribute("command", command);
}
invokee.addEventListener(
- "invoke",
+ "command",
(e) => {
- containedinvoker.setAttribute("invokeaction", "show");
+ containedinvoker.setAttribute("command", "show");
},
{ once: true },
);
@@ -234,9 +206,7 @@
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
},
- `invoking to close (with invokeaction ${setType} as ${
- action == null ? "auto" : action || "explicit empty"
- }) open modal dialog while changing action still closes`,
+ `invoking to close (with command ${setType} as ${command}) open modal dialog while changing command still closes`,
);
});
});
@@ -245,7 +215,7 @@
promise_test(async function (t) {
t.add_cleanup(resetState);
- containedinvoker.setAttribute("invokeaction", "showModal");
+ containedinvoker.setAttribute("command", "showModal");
invokee.show();
assert_true(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
@@ -256,25 +226,25 @@
promise_test(async function (t) {
t.add_cleanup(resetState);
- containedinvoker.setAttribute("invokeaction", "showmodal");
+ containedinvoker.setAttribute("command", "showmodal");
invokee.showModal();
assert_true(invokee.open, "invokee.open");
assert_true(invokee.matches(":modal"), "invokee :modal");
invokee.addEventListener(
- "invoke",
+ "command",
(e) => {
- containedinvoker.setAttribute("invokeaction", "close");
+ containedinvoker.setAttribute("command", "close");
},
{ once: true },
);
await clickOn(invokerbutton);
assert_true(invokee.open, "invokee.open");
assert_true(invokee.matches(":modal"), "invokee :modal");
- }, "invoking (as showmodal) open modal, while changing action still a no-op");
+ }, "invoking (as showmodal) open modal, while changing command still a no-op");
promise_test(async function (t) {
t.add_cleanup(resetState);
- invokerbutton.setAttribute("invokeaction", "showmodal");
+ invokerbutton.setAttribute("command", "showmodal");
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
invokee.setAttribute("popover", "auto");
@@ -287,7 +257,7 @@
promise_test(async function (t) {
t.add_cleanup(resetState);
- invokerbutton.setAttribute("invokeaction", "close");
+ invokerbutton.setAttribute("command", "close");
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
await clickOn(containedinvoker);
@@ -296,21 +266,21 @@
}, "invoking (as close) already closed dialog is noop");
// Open Popovers using Dialog actions
- ["showmodal", "close", ""].forEach((action) => {
+ ["showmodal", "close"].forEach((command) => {
["manual", "auto"].forEach((popoverState) => {
promise_test(
async function (t) {
t.add_cleanup(resetState);
invokee.setAttribute("popover", popoverState);
invokee.showPopover();
- containedinvoker.setAttribute("invokeaction", action);
+ containedinvoker.setAttribute("command", command);
assert_true(
invokee.matches(":popover-open"),
"invokee :popover-open",
);
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
- invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
await clickOn(containedinvoker);
@@ -321,15 +291,13 @@
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
},
- `invoking (as ${
- action || "explicit empty"
- }) dialog as open popover=${popoverState} is noop`,
+ `invoking (as ${command}) dialog as open popover=${popoverState} is noop`,
);
});
});
// Elements being disconnected during invoke steps
- ["showmodal", "close", ""].forEach((action) => {
+ ["showmodal", "close"].forEach((command) => {
promise_test(
async function (t) {
t.add_cleanup(() => {
@@ -337,11 +305,11 @@
resetState();
});
const invokee = document.querySelector("#invokee");
- invokerbutton.setAttribute("invokeaction", action);
+ invokerbutton.setAttribute("command", command);
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
invokee.addEventListener(
- "invoke",
+ "command",
(e) => {
invokee.remove();
},
@@ -353,42 +321,36 @@
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
},
- `invoking (as ${
- action || "explicit empty"
- }) dialog that is removed is noop`,
+ `invoking (as ${command}) dialog that is removed is noop`,
);
promise_test(
async function (t) {
const invokerbutton = document.createElement("button");
- invokerbutton.invokeTargetElement = invokee;
- invokerbutton.setAttribute("invokeaction", action);
+ invokerbutton.commandForElement = invokee;
+ invokerbutton.setAttribute("command", command);
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
await clickOn(invokerbutton);
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
},
- `invoking (as ${
- action || "explicit empty"
- }) dialog from a detached invoker`,
+ `invoking (as ${command}) dialog from a detached invoker`,
);
promise_test(
async function (t) {
const invokerbutton = document.createElement("button");
const invokee = document.createElement("dialog");
- invokerbutton.invokeTargetElement = invokee;
- invokerbutton.setAttribute("invokeaction", action);
+ invokerbutton.commandForElementt = invokee;
+ invokerbutton.setAttribute("command", command);
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
await clickOn(invokerbutton);
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
},
- `invoking (as ${
- action || "explicit empty"
- }) detached dialog from a detached invoker`,
+ `invoking (as ${command}) detached dialog from a detached invoker`,
);
});
</script>
diff --git a/tests/wpt/tests/html/semantics/invokers/invoketarget-on-dialog-invalid-behavior.tentative.html b/tests/wpt/tests/html/semantics/invokers/invoketarget-on-dialog-invalid-behavior.tentative.html
index af84c22594a..8ea5066dd74 100644
--- a/tests/wpt/tests/html/semantics/invokers/invoketarget-on-dialog-invalid-behavior.tentative.html
+++ b/tests/wpt/tests/html/semantics/invokers/invoketarget-on-dialog-invalid-behavior.tentative.html
@@ -11,21 +11,22 @@
<script src="resources/invoker-utils.js"></script>
<dialog id="invokee">
- <button id="containedinvoker" invoketarget="invokee"></button>
+ <button id="containedinvoker" commandfor="invokee" command="close"></button>
</dialog>
-<button id="invokerbutton" invoketarget="invokee"></button>
+<button id="invokerbutton" commandfor="invokee" command="showmodal"></button>
<script>
function resetState() {
invokee.close();
try { invokee.hidePopover(); } catch {}
invokee.removeAttribute("popover");
- invokerbutton.removeAttribute("invokeaction");
- containedinvoker.removeAttribute("invokeaction");
+ invokerbutton.removeAttribute("command");
+ containedinvoker.removeAttribute("command");
}
// invalid
[
+ "",
"foo",
"foo-bar",
"auto",
@@ -36,7 +37,7 @@
].forEach((action) => {
promise_test(async function (t) {
t.add_cleanup(resetState);
- invokerbutton.setAttribute("invokeaction", action);
+ invokerbutton.setAttribute("command", action);
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
await clickOn(invokerbutton);
@@ -46,7 +47,7 @@
promise_test(async function (t) {
t.add_cleanup(resetState);
- containedinvoker.setAttribute("invokeaction", action);
+ containedinvoker.setAttribute("command", action);
invokee.show();
assert_true(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
@@ -57,7 +58,7 @@
promise_test(async function (t) {
t.add_cleanup(resetState);
- containedinvoker.setAttribute("invokeaction", action);
+ containedinvoker.setAttribute("command", action);
invokee.showModal();
assert_true(invokee.open, "invokee.open");
assert_true(invokee.matches(":modal"), "invokee :modal");
@@ -68,14 +69,14 @@
promise_test(async function (t) {
t.add_cleanup(resetState);
- containedinvoker.setAttribute("invokeaction", action);
+ containedinvoker.setAttribute("command", action);
invokee.showModal();
assert_true(invokee.open, "invokee.open");
assert_true(invokee.matches(":modal"), "invokee :modal");
invokee.addEventListener(
- "invoke",
+ "command",
(e) => {
- containedinvoker.setAttribute("invokeaction", "");
+ containedinvoker.setAttribute("command", "");
},
{ once: true },
);
@@ -86,21 +87,21 @@
});
// Open Popovers using Dialog actions
- ["showmodal", "close", ""].forEach((action) => {
+ ["showmodal", "close"].forEach((action) => {
["manual", "auto"].forEach((popoverState) => {
promise_test(
async function (t) {
t.add_cleanup(resetState);
invokee.setAttribute("popover", popoverState);
invokee.showPopover();
- containedinvoker.setAttribute("invokeaction", action);
+ containedinvoker.setAttribute("command", action);
assert_true(
invokee.matches(":popover-open"),
"invokee :popover-open",
);
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
- invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
await clickOn(containedinvoker);
diff --git a/tests/wpt/tests/html/semantics/invokers/invoketarget-on-input-number.tentative.html b/tests/wpt/tests/html/semantics/invokers/invoketarget-on-input-number.tentative.html
index b06053b9f1f..643423131eb 100644
--- a/tests/wpt/tests/html/semantics/invokers/invoketarget-on-input-number.tentative.html
+++ b/tests/wpt/tests/html/semantics/invokers/invoketarget-on-input-number.tentative.html
@@ -10,12 +10,12 @@
<script src="resources/invoker-utils.js"></script>
<input type="number" id="invokee" value="0">
-<button id="invokerbutton" invoketarget="invokee"></button>
+<button id="invokerbutton" commandfor="invokee"></button>
<script>
function reset() {
invokee.value = 0;
- invokerbutton.removeAttribute('invokeaction');
+ invokerbutton.removeAttribute('command');
}
// stepUp
@@ -23,7 +23,7 @@
promise_test(async function (t) {
t.add_cleanup(reset);
assert_equals(invokee.valueAsNumber, 0);
- invokerbutton.setAttribute("invokeaction", "stepup");
+ invokerbutton.setAttribute("command", "stepup");
await clickOn(invokerbutton);
assert_equals(invokee.valueAsNumber, 1);
}, "invoking number input with stepup action increments value");
@@ -31,7 +31,7 @@
promise_test(async function (t) {
t.add_cleanup(reset);
assert_equals(invokee.valueAsNumber, 0);
- invokerbutton.setAttribute("invokeaction", "sTePuP");
+ invokerbutton.setAttribute("command", "sTePuP");
await clickOn(invokerbutton);
assert_equals(invokee.valueAsNumber, 1);
}, "invoking number input with stepup action (case-insensitive) increments value");
@@ -39,8 +39,8 @@
promise_test(async function (t) {
t.add_cleanup(reset);
assert_equals(invokee.valueAsNumber, 0);
- invokerbutton.setAttribute("invokeaction", "stepup");
- invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ invokerbutton.setAttribute("command", "stepup");
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
await clickOn(invokerbutton);
@@ -52,7 +52,7 @@
promise_test(async function (t) {
t.add_cleanup(reset);
assert_equals(invokee.valueAsNumber, 0);
- invokerbutton.setAttribute("invokeaction", "stepdown");
+ invokerbutton.setAttribute("command", "stepdown");
await clickOn(invokerbutton);
assert_equals(invokee.valueAsNumber, -1);
}, "invoking number input with stepdown action decrements value");
@@ -60,7 +60,7 @@
promise_test(async function (t) {
t.add_cleanup(reset);
assert_equals(invokee.valueAsNumber, 0);
- invokerbutton.setAttribute("invokeaction", "sTePdOwN");
+ invokerbutton.setAttribute("command", "sTePdOwN");
await clickOn(invokerbutton);
assert_equals(invokee.valueAsNumber, -1);
}, "invoking number input with stepdown action (case-insensitive) decrements value");
@@ -68,8 +68,8 @@
promise_test(async function (t) {
t.add_cleanup(reset);
assert_equals(invokee.valueAsNumber, 0);
- invokerbutton.setAttribute("invokeaction", "stepdown");
- invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ invokerbutton.setAttribute("command", "stepdown");
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
await clickOn(invokerbutton);
diff --git a/tests/wpt/tests/html/semantics/invokers/invoketarget-on-popover-behavior.tentative.html b/tests/wpt/tests/html/semantics/invokers/invoketarget-on-popover-behavior.tentative.html
index f414559e55a..c974b6ff108 100644
--- a/tests/wpt/tests/html/semantics/invokers/invoketarget-on-popover-behavior.tentative.html
+++ b/tests/wpt/tests/html/semantics/invokers/invoketarget-on-popover-behavior.tentative.html
@@ -11,135 +11,133 @@
<script src="resources/invoker-utils.js"></script>
<div id="invokee" popover>
- <button id="containedinvoker" invoketarget="invokee"></button>
+ <button id="containedinvoker" commandfor="invokee" command="hidepopover"></button>
</div>
-<button id="invokerbutton" invoketarget="invokee"></button>
+<button id="invokerbutton" commandfor="invokee" command="togglepopover"></button>
<script>
- // auto
+ function resetState() {
+ invokerbutton.setAttribute("commandfor", "invokee");
+ invokerbutton.setAttribute("command", "togglepopover");
+ containedinvoker.setAttribute("commandfor", "invokee");
+ containedinvoker.setAttribute("command", "closepopover");
+ try {
+ invokee.hidePopover();
+ } catch {}
+ invokee.setAttribute("popover", "");
+ }
promise_test(async function (t) {
assert_false(invokee.matches(":popover-open"));
- invokee.addEventListener("invoke", (e) => { invokerbutton.setAttribute('invokeaction', 'hidepopover'); }, {
+ invokee.addEventListener("command", (e) => { invokerbutton.setAttribute('command', 'hidepopover'); }, {
once: true,
});
await clickOn(invokerbutton);
- t.add_cleanup(() => {
- invokee.hidePopover();
- invokerbutton.removeAttribute("invokeaction");
- });
+ t.add_cleanup(resetState);
assert_true(invokee.matches(":popover-open"));
- }, "changing invokeaction attribute inside invokeevent doesn't impact the invocation");
-
- function resetState() {
- invokerbutton.removeAttribute("invokeaction");
- containedinvoker.removeAttribute("invokeaction");
- try {
- invokee.hidePopover();
- } catch {}
- invokee.setAttribute("popover", "");
- }
+ }, "changing command attribute inside invokeevent doesn't impact the invocation");
// Open actions
[
- null,
- "",
"togglepopover",
"showpopover",
/* test case sensitivity */
"tOgGlEpOpOvEr",
"sHoWpOpOvEr",
- ].forEach((action) => {
+ ].forEach((command) => {
promise_test(
async function (t) {
t.add_cleanup(resetState);
- if (action !== null) invokerbutton.invokeAction = action;
+ invokerbutton.command = command;
assert_false(invokee.matches(":popover-open"));
await clickOn(invokerbutton);
assert_true(invokee.matches(":popover-open"));
},
- `invoking (as ${
- action === null ? "auto" : action || "explicit empty"
- }) closed popover opens`,
+ `invoking (as ${command}) closed popover opens`,
);
promise_test(
async function (t) {
t.add_cleanup(resetState);
- if (action !== null) invokerbutton.invokeAction = action;
+ invokerbutton.command = command;
assert_false(invokee.matches(":popover-open"));
- invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
await clickOn(invokerbutton);
assert_false(invokee.matches(":popover-open"));
},
- `invoking (as ${
- action === null ? "auto" : action || "explicit empty"
- }) closed popover with preventDefault does not open`,
+ `invoking (as ${command}) closed popover with preventDefault does not open`,
);
});
// Close actions
[
- null,
- "",
"togglepopover",
"hidepopover",
/* test case sensitivity */
"tOgGlEpOpOvEr",
"hIdEpOpOvEr",
- ].forEach((action) => {
+ ].forEach((command) => {
promise_test(
async function (t) {
t.add_cleanup(resetState);
- if (action !== null) invokerbutton.invokeAction = action;
+ invokerbutton.command = command;
invokee.showPopover();
assert_true(invokee.matches(":popover-open"));
await clickOn(invokerbutton);
assert_false(invokee.matches(":popover-open"));
},
- `invoking (as ${
- action === null ? "auto" : action || "explicit empty"
- }) open popover closes`,
+ `invoking (as ${command}) open popover closes`,
+ );
+
+ promise_test(
+ async function (t) {
+ t.add_cleanup(resetState);
+ invokerbutton.command = command;
+ invokee.showPopover();
+ assert_true(invokee.matches(":popover-open"));
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
+ once: true,
+ });
+ await clickOn(invokerbutton);
+ assert_true(invokee.matches(":popover-open"));
+ },
+ `invoking (as ${command}) open popover with preventDefault does not close`,
);
promise_test(
async function (t) {
t.add_cleanup(resetState);
- if (action !== null) containedinvoker.invokeAction = action;
+ containedinvoker.command = command;
invokee.showPopover();
assert_true(invokee.matches(":popover-open"));
await clickOn(containedinvoker);
assert_false(invokee.matches(":popover-open"));
},
- `invoking (as ${
- action === null ? "auto" : action || "explicit empty"
- }) from within open popover closes`,
+ `invoking (as ${command}) from within open popover closes`,
);
promise_test(
async function (t) {
t.add_cleanup(resetState);
- if (action !== null) invcontainedinvokervokeaction = action;
+ containedinvoker.command = command;
invokee.showPopover();
- invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
assert_true(invokee.matches(":popover-open"));
await clickOn(containedinvoker);
assert_true(invokee.matches(":popover-open"));
},
- `invoking (as ${
- action === null ? "auto" : action || "explicit empty"
- }) open popover with preventDefault does not close`,
+ `invoking (as ${command}) from within open popover with preventDefault does not close`,
);
});
// showpopover specific
promise_test(async function (t) {
t.add_cleanup(resetState);
- invokerbutton.setAttribute("invokeaction", "showpopover");
+ invokerbutton.setAttribute("command", "showpopover");
invokee.showPopover();
assert_true(invokee.matches(":popover-open"));
await clickOn(invokerbutton);
@@ -149,7 +147,7 @@
// hidepopover specific
promise_test(async function (t) {
t.add_cleanup(resetState);
- invokerbutton.setAttribute("invokeaction", "hidepopover");
+ invokerbutton.setAttribute("command", "hidepopover");
assert_false(invokee.matches(":popover-open"));
await clickOn(invokerbutton);
assert_false(invokee.matches(":popover-open"));
diff --git a/tests/wpt/tests/html/semantics/invokers/invoketarget-on-popover-invalid-behavior.tentative.html b/tests/wpt/tests/html/semantics/invokers/invoketarget-on-popover-invalid-behavior.tentative.html
index 755f3a67770..31442261f37 100644
--- a/tests/wpt/tests/html/semantics/invokers/invoketarget-on-popover-invalid-behavior.tentative.html
+++ b/tests/wpt/tests/html/semantics/invokers/invoketarget-on-popover-invalid-behavior.tentative.html
@@ -11,14 +11,14 @@
<script src="resources/invoker-utils.js"></script>
<div id="invokee" popover>
- <button id="containedinvoker" invoketarget="invokee"></button>
+ <button id="containedinvoker" commandfor="invokee"></button>
</div>
-<button id="invokerbutton" invoketarget="invokee"></button>
+<button id="invokerbutton" commandfor="invokee"></button>
<script>
function resetState() {
- invokerbutton.removeAttribute("invokeaction");
- containedinvoker.removeAttribute("invokeaction");
+ invokerbutton.removeAttribute("command");
+ containedinvoker.removeAttribute("command");
try {
invokee.hidePopover();
} catch {}
@@ -26,22 +26,22 @@
}
// invalid actions on showpopover
- ["foo-bar", "showmodal", "showpicker", "open", "close"].forEach((action) => {
+ [null, "", "foo-bar", "showmodal", "showpicker", "open", "close"].forEach((command) => {
promise_test(async function (t) {
t.add_cleanup(resetState);
- invokerbutton.invokeAction = action;
+ invokerbutton.command = command;
assert_false(invokee.matches(":popover-open"));
await clickOn(invokerbutton);
assert_false(invokee.matches(":popover-open"));
- }, `invoking (as ${action}) on popover does nothing`);
+ }, `invoking (as ${command}) on popover does nothing`);
promise_test(async function (t) {
t.add_cleanup(resetState);
- invokerbutton.invokeAction = action;
+ invokerbutton.command = command;
invokee.showPopover();
assert_true(invokee.matches(":popover-open"));
await clickOn(invokerbutton);
assert_true(invokee.matches(":popover-open"));
- }, `invoking (as ${action}) on open popover does nothing`);
+ }, `invoking (as ${command}) on open popover does nothing`);
});
</script>
diff --git a/tests/wpt/tests/html/semantics/invokers/invoketarget-on-video-behavior.tentative.html b/tests/wpt/tests/html/semantics/invokers/invoketarget-on-video-behavior.tentative.html
index d15d6f95841..e395281ee31 100644
--- a/tests/wpt/tests/html/semantics/invokers/invoketarget-on-video-behavior.tentative.html
+++ b/tests/wpt/tests/html/semantics/invokers/invoketarget-on-video-behavior.tentative.html
@@ -11,38 +11,20 @@
<script src="resources/invoker-utils.js"></script>
<video controls id="invokee" src="/media/movie_5.mp4"></video>
-<button id="invokerbutton" invoketarget="invokee"></button>
+<button id="invokerbutton" commandfor="invokee"></button>
<script>
- // auto
-
- promise_test(async function (t) {
- t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
- invokee.pause();
- invokee.currentTime = 0;
- invokee.muted = false;
- });
- assert_true(invokee.paused);
- invokerbutton.setAttribute("invokeaction", "");
- await clickOn(invokerbutton);
- await new Promise((resolve) => {
- requestAnimationFrame(resolve);
- });
- assert_true(invokee.paused);
- }, "invoking video with auto action is no-op");
-
// playpause
promise_test(async function (t) {
t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
+ invokerbutton.removeAttribute("command");
invokee.pause();
invokee.currentTime = 0;
invokee.muted = false;
});
assert_true(invokee.paused);
- invokerbutton.setAttribute("invokeaction", "playpause");
+ invokerbutton.setAttribute("command", "playpause");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -52,16 +34,16 @@
promise_test(async function (t) {
t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
+ invokerbutton.removeAttribute("command");
invokee.pause();
invokee.currentTime = 0;
invokee.muted = false;
});
- invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
assert_true(invokee.paused);
- invokerbutton.setAttribute("invokeaction", "playpause");
+ invokerbutton.setAttribute("command", "playpause");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -71,7 +53,7 @@
promise_test(async function (t) {
t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
+ invokerbutton.removeAttribute("command");
invokee.pause();
invokee.currentTime = 0;
invokee.muted = false;
@@ -79,7 +61,7 @@
await test_driver.bless("play video");
invokee.play();
assert_false(invokee.paused);
- invokerbutton.setAttribute("invokeaction", "playpause");
+ invokerbutton.setAttribute("command", "playpause");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -91,13 +73,13 @@
promise_test(async function (t) {
t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
+ invokerbutton.removeAttribute("command");
invokee.pause();
invokee.currentTime = 0;
invokee.muted = false;
});
assert_true(invokee.paused);
- invokerbutton.setAttribute("invokeaction", "play");
+ invokerbutton.setAttribute("command", "play");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -107,16 +89,16 @@
promise_test(async function (t) {
t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
+ invokerbutton.removeAttribute("command");
invokee.pause();
invokee.currentTime = 0;
invokee.muted = false;
});
- invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
assert_true(invokee.paused);
- invokerbutton.setAttribute("invokeaction", "play");
+ invokerbutton.setAttribute("command", "play");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -126,7 +108,7 @@
promise_test(async function (t) {
t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
+ invokerbutton.removeAttribute("command");
invokee.pause();
invokee.currentTime = 0;
invokee.muted = false;
@@ -134,7 +116,7 @@
await test_driver.bless("play video");
invokee.play();
assert_false(invokee.paused);
- invokerbutton.setAttribute("invokeaction", "play");
+ invokerbutton.setAttribute("command", "play");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -146,13 +128,13 @@
promise_test(async function (t) {
t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
+ invokerbutton.removeAttribute("command");
invokee.pause();
invokee.currentTime = 0;
invokee.muted = false;
});
assert_true(invokee.paused);
- invokerbutton.setAttribute("invokeaction", "pause");
+ invokerbutton.setAttribute("command", "pause");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -162,16 +144,16 @@
promise_test(async function (t) {
t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
+ invokerbutton.removeAttribute("command");
invokee.pause();
invokee.currentTime = 0;
invokee.muted = false;
});
- invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
assert_true(invokee.paused);
- invokerbutton.setAttribute("invokeaction", "pause");
+ invokerbutton.setAttribute("command", "pause");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -181,7 +163,7 @@
promise_test(async function (t) {
t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
+ invokerbutton.removeAttribute("command");
invokee.pause();
invokee.currentTime = 0;
invokee.muted = false;
@@ -189,7 +171,7 @@
await test_driver.bless("play video");
invokee.play();
assert_false(invokee.paused);
- invokerbutton.setAttribute("invokeaction", "pause");
+ invokerbutton.setAttribute("command", "pause");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -201,13 +183,13 @@
promise_test(async function (t) {
t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
+ invokerbutton.removeAttribute("command");
invokee.pause();
invokee.currentTime = 0;
invokee.muted = false;
});
assert_false(invokee.muted);
- invokerbutton.setAttribute("invokeaction", "toggleMuted");
+ invokerbutton.setAttribute("command", "toggleMuted");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -217,16 +199,16 @@
promise_test(async function (t) {
t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
+ invokerbutton.removeAttribute("command");
invokee.pause();
invokee.currentTime = 0;
invokee.muted = false;
});
- invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
assert_false(invokee.muted);
- invokerbutton.setAttribute("invokeaction", "toggleMuted");
+ invokerbutton.setAttribute("command", "toggleMuted");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
@@ -236,14 +218,14 @@
promise_test(async function (t) {
t.add_cleanup(async () => {
- invokerbutton.removeAttribute("invokeaction");
+ invokerbutton.removeAttribute("command");
invokee.pause();
invokee.currentTime = 0;
invokee.muted = false;
});
invokee.muted = true;
assert_true(invokee.muted);
- invokerbutton.setAttribute("invokeaction", "toggleMuted");
+ invokerbutton.setAttribute("command", "toggleMuted");
await clickOn(invokerbutton);
await new Promise((resolve) => {
requestAnimationFrame(resolve);
diff --git a/tests/wpt/tests/html/semantics/popovers/popover-attribute-basic.html b/tests/wpt/tests/html/semantics/popovers/popover-attribute-basic.html
index 2af3bbc137f..f13a05288c3 100644
--- a/tests/wpt/tests/html/semantics/popovers/popover-attribute-basic.html
+++ b/tests/wpt/tests/html/semantics/popovers/popover-attribute-basic.html
@@ -234,6 +234,31 @@ window.onload = () => {
},`Changing the popover type in a "beforetoggle" event handler should throw an exception (during showPopover())`);
test((t) => {
+ const other_popover = createPopover(t);
+ other_popover.setAttribute('popover','auto');
+ other_popover.showPopover();
+ const popover = createPopover(t);
+ popover.setAttribute('popover','auto');
+ other_popover.addEventListener('beforetoggle', (e) => {
+ if (e.newState !== "closed")
+ return;
+ popover.setAttribute('popover','manual');
+ },{once: true});
+ assert_true(other_popover.matches(':popover-open'));
+ assert_false(popover.matches(':popover-open'));
+
+ popover.id = 'type-change-test';
+ const invoker = document.createElement('button');
+ document.body.appendChild(invoker);
+ t.add_cleanup(() => invoker.remove());
+ invoker.setAttribute('popovertarget', 'type-change-test');
+ invoker.click();
+
+ assert_false(other_popover.matches(':popover-open'),'unrelated popover is hidden');
+ assert_false(popover.matches(':popover-open'),'popover is not shown if its type changed during show');
+ },`Changing the popover type in a "beforetoggle" event handler should not show the popover (during popovertarget invoking)`);
+
+ test((t) => {
const popover = createPopover(t);
popover.setAttribute('popover','auto');
const other_popover = createPopover(t);
diff --git a/tests/wpt/tests/interfaces/invokers.tentative.idl b/tests/wpt/tests/interfaces/invokers.tentative.idl
index eb1b8247f06..4724d7deb08 100644
--- a/tests/wpt/tests/interfaces/invokers.tentative.idl
+++ b/tests/wpt/tests/interfaces/invokers.tentative.idl
@@ -1,15 +1,15 @@
interface mixin InvokerElement {
- [CEReactions,Reflect=invoketarget] attribute Element? invokeTargetElement;
- [CEReactions,Reflect=invokeaction] attribute DOMString invokeAction;
+ [CEReactions,Reflect=invoketarget] attribute Element? commandForElement;
+ [CEReactions,Reflect=invokeaction] attribute DOMString command;
};
-interface InvokeEvent : Event {
- constructor(DOMString type, optional InvokeEventInit eventInitDict = {});
+interface CommandEvent : Event {
+ constructor(DOMString type, optional CommandEventInit eventInitDict = {});
readonly attribute Element? invoker;
- readonly attribute DOMString action;
+ readonly attribute DOMString command;
};
-dictionary InvokeEventInit : EventInit {
+dictionary CommandEventInit : EventInit {
Element? invoker = null;
- DOMString action = "";
+ DOMString command = "";
};
diff --git a/tests/wpt/tests/intersection-observer/v2/box-reflect.html b/tests/wpt/tests/intersection-observer/v2/box-reflect.html
new file mode 100644
index 00000000000..41e072a587c
--- /dev/null
+++ b/tests/wpt/tests/intersection-observer/v2/box-reflect.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/intersection-observer-test-utils.js"></script>
+
+<style>
+body, html {
+ margin: 0;
+}
+pre, #log {
+ position: absolute;
+ top: 0;
+ left: 200px;
+}
+#reflect {
+ width: 100px;
+ height: 100px;
+ background-color: hotpink;
+ -webkit-box-reflect: below 10px;
+ margin: 10px 0;
+}
+</style>
+
+<div id="reflect"></div>
+<div id="target">Hello, world!</div>
+
+<script>
+var delay = 100;
+var entries = [];
+var target;
+var supported = CSS.supports("-webkit-box-reflect", "below 10px");
+
+runTestCycle(function() {
+ target = document.getElementById("target");
+ assert_true(!!target, "target exists");
+ let observer = new IntersectionObserver(function(changes) {
+ entries = entries.concat(changes)
+ }, {trackVisibility: true, delay: delay});
+ observer.observe(target);
+ entries = entries.concat(observer.takeRecords());
+ assert_equals(entries.length, 0, "No initial notifications.");
+ runTestCycle(step0, "First rAF.", delay);
+}, "IntersectionObserverV2 detects occlusion from -webkit-box-reflect (if supported).", delay);
+
+function step0() {
+ assert_equals(entries.length, 1, "Initial notification.");
+ assert_equals(entries[0].isVisible, !supported, "Occluded if -webkit-box-reflect is supported.");
+}
+</script>
diff --git a/tests/wpt/tests/lint.ignore b/tests/wpt/tests/lint.ignore
index 170f4136d4b..ca22175f33c 100644
--- a/tests/wpt/tests/lint.ignore
+++ b/tests/wpt/tests/lint.ignore
@@ -162,6 +162,8 @@ SET TIMEOUT: custom-elements/scoped-registry/scoped-registry-define-upgrade-crit
SET TIMEOUT: encrypted-media/polyfill/chrome-polyfill.js
SET TIMEOUT: encrypted-media/polyfill/clearkey-polyfill.js
SET TIMEOUT: encrypted-media/scripts/playback-temporary-events.js
+SET TIMEOUT: fedcm/support/fedcm-iframe.html
+SET TIMEOUT: fedcm/support/fedcm/disconnect-iframe.html
SET TIMEOUT: fetch/fetch-later/resources/fetch-later-helper.js
SET TIMEOUT: fetch/metadata/resources/helper.sub.js
SET TIMEOUT: fetch/metadata/resources/message-opener.html
@@ -294,8 +296,6 @@ SET TIMEOUT: workers/*
SET TIMEOUT: xhr/resources/init.htm
SET TIMEOUT: xhr/resources/xmlhttprequest-timeout.js
SET TIMEOUT: fenced-frame/resolve-to-config-promise.https.html
-SET TIMEOUT: credential-management/support/fedcm-iframe.html
-SET TIMEOUT: credential-management/support/fedcm/disconnect-iframe.html
# generate_tests implementation and sample usage
GENERATE_TESTS: resources/test/tests/functional/generate-callback.html
@@ -405,7 +405,7 @@ SET TIMEOUT: speculation-rules/prerender/resources/media-autoplay-attribute.html
SET TIMEOUT: speculation-rules/prerender/resources/media-play.html
SET TIMEOUT: html/browsers/browsing-the-web/back-forward-cache/timers.html
SET TIMEOUT: dom/abort/crashtests/timeout-close.html
-SET TIMEOUT: storage-access-api/storage-access-beyond-cookies.locks.tentative.sub.https.window.js
+SET TIMEOUT: storage-access-api/storage-access-beyond-cookies.locks.sub.https.window.js
# setTimeout use in reftests
SET TIMEOUT: acid/acid3/test.html
@@ -608,7 +608,7 @@ WEB-PLATFORM.TEST:web-bundle/subresource-loading/resources/*.js
# https://github.com/web-platform-tests/wpt/issues/16455
# Please consult with ecosystem-infra@chromium.org before adding more.
MISSING DEPENDENCY: credential-management/support/otpcredential-helper.js
-MISSING DEPENDENCY: credential-management/support/fedcm-mock.js
+MISSING DEPENDENCY: fedcm/support/fedcm-mock.js
MISSING DEPENDENCY: resources/chromium/content-index-helpers.js
MISSING DEPENDENCY: resources/chromium/contacts_manager_mock.js
MISSING DEPENDENCY: resources/chromium/web-bluetooth-test.js
diff --git a/tests/wpt/tests/long-animation-frame/tentative/loaf-pointer-without-render-iframe.html b/tests/wpt/tests/long-animation-frame/tentative/loaf-pointer-without-render-iframe.html
new file mode 100644
index 00000000000..1fb106403a1
--- /dev/null
+++ b/tests/wpt/tests/long-animation-frame/tentative/loaf-pointer-without-render-iframe.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Long Animation Frame Timing: Pointer events without render in an iframe</title>
+<meta name="timeout" content="long">
+<script src=/resources/testdriver.js></script>
+<script src=/resources/testdriver-actions.js></script>
+<script src=/resources/testdriver-vendor.js></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<body>
+<script>
+const BUSY_DURATION = 60;
+function event_handler() {
+ busy_wait(BUSY_DURATION);
+}
+
+function click_iframe(iframe) {
+ return new test_driver.Actions()
+ .pointerMove(10, 10, {origin: iframe})
+ .pointerDown()
+ .pointerUp()
+ .send();
+}
+
+promise_test(async t => {
+ const iframe = document.createElement("iframe");
+ iframe.src = "resources/long-pointerdown.html"
+ t.add_cleanup(() => iframe.remove());
+ document.body.append(iframe);
+ await new Promise(resolve => iframe.addEventListener("load", resolve));
+ iframe.focus();
+ await click_iframe(iframe);
+ await new Promise(resolve => requestAnimationFrame(() => resolve()));
+ await new Promise(resolve => t.step_timeout(resolve, 0));
+ const loaf_promise = new Promise(resolve => {
+ function on_message(event) {
+ resolve(event.data);
+ }
+
+ addEventListener("message", on_message);
+ t.add_cleanup(() => removeEventListener("message", on_message));
+
+ new PerformanceObserver(entries => {
+ for (const e of entries.getEntries())
+ if (entries.getEntries().some(
+ e => !e.renderStart && e.scripts.some(script => script.invoker === "BODY.onpointerdown")))
+ resolve("MAIN");
+ }).observe({type: "long-animation-frame"});
+ });
+
+ await click_iframe(iframe);
+
+ assert_equals(await loaf_promise, "FRAME");
+}, "Input events in iframes that don't cause a render should only create LoAF for the iframe")
+</script>
+</body>
diff --git a/tests/wpt/tests/long-animation-frame/tentative/loaf-pointer-without-render.html b/tests/wpt/tests/long-animation-frame/tentative/loaf-pointer-without-render.html
new file mode 100644
index 00000000000..f05cb218ba6
--- /dev/null
+++ b/tests/wpt/tests/long-animation-frame/tentative/loaf-pointer-without-render.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Long Animation Frame Timing: Pointer event without render in the main frame</title>
+<meta name="timeout" content="long">
+<script src=/resources/testdriver.js></script>
+<script src=/resources/testdriver-actions.js></script>
+<script src=/resources/testdriver-vendor.js></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<style>
+ * {
+ user-select: none;
+ }
+</style>
+<body>
+<h1>Test</h1>
+<script>
+promise_test(async t => {
+ document.body.addEventListener("pointerdown", () => {
+ busy_wait(60);
+ });
+ const loaf_promise = new Promise(resolve => new PerformanceObserver(entries => {
+ if (entries.getEntries().some(
+ e => e.scripts.some(script => script.invoker === "BODY.onpointerdown"))) {
+ resolve("OK");
+ }
+ }).observe({type: "long-animation-frame"}));
+
+ const actions = new test_driver.Actions();
+ await actions.pointerMove(10, 10, {origin: document.body})
+ .pointerDown()
+ .pointerUp()
+ .send();
+ assert_equals(await loaf_promise, "OK");
+}, "Input events should create a LoAF even if they don't generate a frame")
+</script>
+</body>
diff --git a/tests/wpt/tests/long-animation-frame/tentative/resources/long-pointerdown.html b/tests/wpt/tests/long-animation-frame/tentative/resources/long-pointerdown.html
new file mode 100644
index 00000000000..34e8115086c
--- /dev/null
+++ b/tests/wpt/tests/long-animation-frame/tentative/resources/long-pointerdown.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<head>
+ <script src="utils.js"></script>
+ <style>
+ body {
+ width: 200px;
+ height: 200px;
+ }
+
+ * {
+ user-select: none;
+ }
+ </style>
+<body>
+ <script>
+ document.body.addEventListener("pointerdown", () => {
+ busy_wait(120)
+ });
+ new PerformanceObserver(entries => {
+ if (entries.getEntries().some(
+ e => e.scripts.some(script => script.invoker === "BODY.onpointerdown"))) {
+ window.parent.postMessage("FRAME", "*");
+ }
+ }).observe({type: "long-animation-frame"});
+ </script>
+</body>
diff --git a/tests/wpt/tests/long-animation-frame/tentative/resources/utils.js b/tests/wpt/tests/long-animation-frame/tentative/resources/utils.js
index aa537d39a78..b51ab891dcd 100644
--- a/tests/wpt/tests/long-animation-frame/tentative/resources/utils.js
+++ b/tests/wpt/tests/long-animation-frame/tentative/resources/utils.js
@@ -1,7 +1,9 @@
const windowLoaded = new Promise(resolve => window.addEventListener('load', resolve));
-setup(() =>
- assert_implements(window.PerformanceLongAnimationFrameTiming,
- 'Long animation frames are not supported.'));
+if ("setup" in globalThis) {
+ setup(() =>
+ assert_implements(window.PerformanceLongAnimationFrameTiming,
+ 'Long animation frames are not supported.'));
+}
const very_long_frame_duration = 360;
const no_long_frame_timeout = very_long_frame_duration * 2;
diff --git a/tests/wpt/tests/mathml/presentation-markup/mrow/mrow-fallback.html b/tests/wpt/tests/mathml/presentation-markup/mrow/mrow-fallback.html
index 3f9d466148c..a7e984328c5 100644
--- a/tests/wpt/tests/mathml/presentation-markup/mrow/mrow-fallback.html
+++ b/tests/wpt/tests/mathml/presentation-markup/mrow/mrow-fallback.html
@@ -11,6 +11,10 @@
<script src="/mathml/support/fonts.js"></script>
<script src="/mathml/support/layout-comparison.js"></script>
<script src="/mathml/support/mathml-fragments.js"></script>
+<style>
+ /* Revert style specified in the UA style sheet that changes box size. */
+ mfrac { padding-inline: 0; }
+</style>
<script>
setup({ explicit_done: true });
window.addEventListener("load", () => { loadAllFonts().then(runTests); });
diff --git a/tests/wpt/tests/mathml/relations/css-styling/padding-border-margin/padding-border-margin-004-ref.html b/tests/wpt/tests/mathml/relations/css-styling/padding-border-margin/padding-border-margin-004-ref.html
new file mode 100644
index 00000000000..4da9af7ffc3
--- /dev/null
+++ b/tests/wpt/tests/mathml/relations/css-styling/padding-border-margin/padding-border-margin-004-ref.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>Padding/border/margin on an mi with italic mathvariant (reference)</title>
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-italic.woff");
+ }
+ math {
+ font-family: TestFont;
+ font-size: 300px;
+ }
+</style>
+<body>
+ <p>This test passes if you see the text <code>1d434</code> in cyan on a blue
+ background, surrounded by a 10px padding, surrounded by a 10px
+ yellow dashed border, itself surrounded by a 10px pink margin.</p>
+ <div style="background: pink; position: absolute; left: 10px; top: 4em;">
+ <math>
+ <mtext style="background: blue; border: 10px dashed yellow; padding: 10px; margin: 10px; color: cyan;">&#x1D434;</mtext>
+ </math>
+ </div>
+</body>
+</html>
diff --git a/tests/wpt/tests/mathml/relations/css-styling/padding-border-margin/padding-border-margin-004.html b/tests/wpt/tests/mathml/relations/css-styling/padding-border-margin/padding-border-margin-004.html
new file mode 100644
index 00000000000..ee8196d8597
--- /dev/null
+++ b/tests/wpt/tests/mathml/relations/css-styling/padding-border-margin/padding-border-margin-004.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>Padding/border/margin on an mi with italic mathvariant</title>
+<link rel="help" href="https://w3c.github.io/mathml-core/#layout-algorithms">
+<link rel="help" href="https://w3c.github.io/mathml-core/#css-styling">
+<link rel="help" href="https://w3c.github.io/mathml-core/#the-mathvariant-attribute">
+<link rel="help" href="https://w3c.github.io/mathml-core/#new-text-transform-values">
+<link rel="help" href="https://w3c.github.io/mathml-core/#italic-mappings">
+<link rel="match" href="padding-border-margin-004-ref.html"/>
+<meta name="assert" content="Verify visual rendering of padding/border/margin on an mi with italic mathvariant.">
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-italic.woff");
+ }
+ math {
+ font-family: TestFont;
+ font-size: 300px;
+ }
+</style>
+<body>
+ <p>This test passes if you see the text <code>1d434</code> in cyan on a blue
+ background, surrounded by a 10px padding, surrounded by a 10px
+ yellow dashed border, itself surrounded by a 10px pink margin.</p>
+ <div style="background: pink; position: absolute; left: 10px; top: 4em;">
+ <math>
+ <mi style="background: blue; border: 10px dashed yellow; padding: 10px; margin: 10px; color: cyan;">&#x41;</mi>
+ </math>
+ </div>
+</body>
+</html>
diff --git a/tests/wpt/tests/permissions-policy/payment-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html b/tests/wpt/tests/permissions-policy/payment-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html
index b95deed7cef..57ae4fbee45 100644
--- a/tests/wpt/tests/permissions-policy/payment-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html
+++ b/tests/wpt/tests/permissions-policy/payment-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html
@@ -3,12 +3,13 @@
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src=/permissions-policy/resources/permissions-policy.js></script>
+ <script src=/common/get-host-info.sub.js></script>
<script>
'use strict';
var relative_path = '/permissions-policy/resources/permissions-policy-payment.html';
var base_src = '/permissions-policy/resources/redirect-on-load.html#';
var same_origin_src = base_src + relative_path;
- var cross_origin_src = base_src + 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ var cross_origin_src = base_src + get_host_info().REMOTE_ORIGIN +
relative_path;
var header = 'permissions policy allow="payment"';
diff --git a/tests/wpt/tests/resources/chromium/web-bluetooth-test.js b/tests/wpt/tests/resources/chromium/web-bluetooth-test.js
index ecea5e760c6..67c3c4ee1b4 100644
--- a/tests/wpt/tests/resources/chromium/web-bluetooth-test.js
+++ b/tests/wpt/tests/resources/chromium/web-bluetooth-test.js
@@ -603,7 +603,7 @@ async function initializeChromiumResources() {
content.mojom = await import(
'/gen/content/web_test/common/fake_bluetooth_chooser.mojom.m.js');
bluetooth.mojom = await import(
- '/gen/device/bluetooth/public/mojom/test/fake_bluetooth.mojom.m.js');
+ '/gen/device/bluetooth/public/mojom/emulation/fake_bluetooth.mojom.m.js');
const map = MOJO_CHOOSER_EVENT_TYPE_MAP;
const types = content.mojom.ChooserEventType;
diff --git a/tests/wpt/tests/sanitizer-api/sanitizer-names.https.html b/tests/wpt/tests/sanitizer-api/sanitizer-names.https.html
index cd33bbc7635..78f1d605a19 100644
--- a/tests/wpt/tests/sanitizer-api/sanitizer-names.https.html
+++ b/tests/wpt/tests/sanitizer-api/sanitizer-names.https.html
@@ -140,7 +140,7 @@
test(t => {
const elems = ["svg:" + elem];
assert_array_equals(
- new Sanitizer({elements: elems}).getConfiguration().allowElements,
+ new Sanitizer({elements: elems}).getConfiguration().allowElements.toSorted(),
elems);
}, `Mixed case element names #${index}: "${elem}" is preserved in config.`);
});
diff --git a/tests/wpt/tests/selection/caret/after-designMode-off-ref.html b/tests/wpt/tests/selection/caret/after-designMode-off-ref.html
new file mode 100644
index 00000000000..fbb3c2467e8
--- /dev/null
+++ b/tests/wpt/tests/selection/caret/after-designMode-off-ref.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<style>
+* { color: green; }
+</style>
+</head>
+<body>&lt;-- No caret should appear here.</body>
+</html>
diff --git a/tests/wpt/tests/selection/caret/after-designMode-off.html b/tests/wpt/tests/selection/caret/after-designMode-off.html
new file mode 100644
index 00000000000..c5ccd66cc4b
--- /dev/null
+++ b/tests/wpt/tests/selection/caret/after-designMode-off.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html class="reftest-wait">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<link rel="match" href="after-designMode-off-ref.html">
+<style>
+:read-only { color: green; }
+:read-write { color: red; }
+</style>
+<script>
+addEventListener("load", () => {
+ requestAnimationFrame(()=> {
+ document.designMode='on';
+ getSelection().collapse(document.body.firstChild, 0);
+ requestAnimationFrame(()=> {
+ document.designMode='off';
+ document.documentElement.removeAttribute("class");
+ });
+ });
+}, {once: true});
+</script>
+</head>
+<body>&lt;-- No caret should appear here.</body>
+</html>
diff --git a/tests/wpt/tests/selection/onselectionchange-on-document.html b/tests/wpt/tests/selection/onselectionchange-on-document.html
index 4e061653777..af522c3f79c 100644
--- a/tests/wpt/tests/selection/onselectionchange-on-document.html
+++ b/tests/wpt/tests/selection/onselectionchange-on-document.html
@@ -11,7 +11,7 @@ promise_test(() => {
return new Promise(resolve => {
let didFireSelectionChangeEvent = false;
document.addEventListener("selectionchange", () => { didFireSelectionChangeEvent = true; resolve(); }, {once: true});
- getSelection().setPosition(container, 0);
+ getSelection().setPosition(container, 1);
assert_false(didFireSelectionChangeEvent);
});
}, "selectionchange event on document fires");
@@ -21,7 +21,7 @@ promise_test(() => {
let selectionChangeCount = 0;
document.addEventListener("selectionchange", () => ++selectionChangeCount);
container.innerHTML = '<span><br></span><span><br></span>';
- getSelection().setPosition(container, 0);
+ getSelection().setPosition(container, 1);
assert_equals(selectionChangeCount, 0);
getSelection().setPosition(container, 2);
assert_equals(selectionChangeCount, 0);
@@ -35,7 +35,7 @@ promise_test(() => {
let selectionChangeCount = 0;
document.addEventListener("selectionchange", () => ++selectionChangeCount);
container.innerHTML = '<span><br></span><span><br></span>';
- getSelection().setPosition(container, 0);
+ getSelection().setPosition(container, 1);
assert_equals(selectionChangeCount, 0);
getSelection().setPosition(container, 2);
assert_equals(selectionChangeCount, 0);
@@ -61,7 +61,7 @@ promise_test(() => {
++selectionChangeCount;
});
container.innerHTML = '<b><br></b><b><br></b>';
- getSelection().setPosition(container, 0);
+ getSelection().setPosition(container, 1);
assert_equals(selectionChangeCount, 0);
await new Promise(setTimeout);
assert_equals(selectionChangeCount, 1);
diff --git a/tests/wpt/tests/shadow-dom/selection-direction.tentative.html b/tests/wpt/tests/shadow-dom/selection-direction.tentative.html
index 6ec9b1934f9..340711a8f06 100644
--- a/tests/wpt/tests/shadow-dom/selection-direction.tentative.html
+++ b/tests/wpt/tests/shadow-dom/selection-direction.tentative.html
@@ -3,7 +3,7 @@
<body>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<meta name="assert" content="Selection's direction should return none, forwad, or backward">
-<link rel="help" href="https://w3c.github.io/selection-api/#dom-selection-getcomposedrange">
+<link rel="help" href="https://w3c.github.io/selection-api/#dom-selection-direction">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
@@ -61,63 +61,6 @@ test(() => {
assert_equals(getSelection().direction, 'backward');
}, 'direction returns "backward" when there is a forward selection that crosses shadow boundaries');
-promise_test(async () => {
- container.innerHTML = 'hello, world';
- const doubleClick = new test_driver.Actions()
- .pointerMove(0, 0, container.firstChild)
- .pointerDown()
- .pointerUp()
- .pointerDown()
- .pointerUp()
- .send();
- await doubleClick;
-
- const sel = getSelection();
- assert_equals(sel.anchorNode, container.firstChild);
- assert_equals(sel.anchorOffset, 0);
- assert_equals(sel.focusNode, container.firstChild);
- assert_equals(sel.focusOffset, 5); // hello
- assert_equals(sel.direction, 'none');
-}, 'direction returns "none" when there is a double click selection(directionless)');
-
-promise_test(async () => {
- container.innerHTML = 'hello, world';
- const tripleClick = new test_driver.Actions()
- .pointerMove(0, 0, container.firstChild)
- .pointerDown()
- .pointerUp()
- .pointerDown()
- .pointerUp()
- .pointerDown()
- .pointerUp()
- .send();
- await tripleClick;
-
- const sel = getSelection();
- assert_equals(sel.anchorNode, container);
- assert_equals(sel.anchorOffset, 0);
- assert_equals(sel.focusNode, container);
- assert_equals(sel.focusOffset, 1);
- assert_equals(sel.direction, 'none');
-}, 'direction returns "none" when there is a triple click selection(directionless)');
-
-promise_test(async () => {
- container.innerHTML = 'hello, world';
- const click = new test_driver.Actions()
- .pointerMove(0, 0, container.firstChild)
- .pointerDown()
- .pointerUp()
- .send();
- await click;
-
- const sel = getSelection();
- assert_equals(sel.anchorNode, container.firstChild);
- assert_equals(sel.anchorOffset, 0);
- assert_equals(sel.focusNode, container.firstChild);
- assert_equals(sel.focusOffset, 0);
- assert_true(sel.isCollapsed);
- assert_equals(sel.direction, 'none');
-}, 'direction returns "none" when the selection is collapsed');
</script>
</body>
</html>
diff --git a/tests/wpt/tests/storage-access-api/resources/storage-access-beyond-cookies-iframe-iframe.html b/tests/wpt/tests/storage-access-api/resources/storage-access-beyond-cookies-iframe-iframe.html
index cfc7d599a1b..44ac9cd8986 100644
--- a/tests/wpt/tests/storage-access-api/resources/storage-access-beyond-cookies-iframe-iframe.html
+++ b/tests/wpt/tests/storage-access-api/resources/storage-access-beyond-cookies-iframe-iframe.html
@@ -9,7 +9,7 @@
const type = (new URLSearchParams(window.location.search)).get("type");
const id = (new URLSearchParams(window.location.search)).get("id");
let message = "HasAccess for " + type;
- // Step 6 (storage-access-api/storage-access-beyond-cookies.{}.tentative.sub.https.html)
+ // Step 6 (storage-access-api/storage-access-beyond-cookies.{}.sub.https.html)
try {
await MaybeSetStorageAccess("*", "*", "blocked");
await test_driver.set_permission({ name: 'storage-access' }, 'granted');
@@ -334,7 +334,7 @@
} catch (_) {
message = "Unable to load handle in same-origin context for " + type;
}
- // Step 7 (storage-access-api/storage-access-beyond-cookies.{}.tentative.sub.https.html)
+ // Step 7 (storage-access-api/storage-access-beyond-cookies.{}.sub.https.html)
await MaybeSetStorageAccess("*", "*", "allowed");
await test_driver.set_permission({ name: 'storage-access' }, 'prompt');
window.top.postMessage({type: "result", message: message}, "*");
diff --git a/tests/wpt/tests/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html b/tests/wpt/tests/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html
index 3d9f5bb1ef2..7d43a06bc7e 100644
--- a/tests/wpt/tests/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html
+++ b/tests/wpt/tests/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html
@@ -13,7 +13,7 @@ window.addEventListener("message", async (e) => {
const type = (new URLSearchParams(window.location.search)).get("type");
const id = (new URLSearchParams(window.location.search)).get("id");
let message = "";
- // Step 4 (storage-access-api/storage-access-beyond-cookies.{}.tentative.sub.https.html)
+ // Step 4 (storage-access-api/storage-access-beyond-cookies.{}.sub.https.html)
try {
await MaybeSetStorageAccess("*", "*", "blocked");
if (type == "cookies") {
@@ -153,7 +153,7 @@ window.addEventListener("message", async (e) => {
window.top.postMessage({type: "result", message: message}, "*");
return;
}
- // Step 5 (storage-access-api/storage-access-beyond-cookies.{}.tentative.sub.https.html)
+ // Step 5 (storage-access-api/storage-access-beyond-cookies.{}.sub.https.html)
let iframe = document.createElement("iframe");
iframe.src = "https://{{hosts[][]}}:{{ports[https][0]}}/storage-access-api/resources/storage-access-beyond-cookies-iframe-iframe.html?type=" + type + "&id=" + id;
document.body.appendChild(iframe);
diff --git a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.BroadcastChannel.tentative.sub.https.window.js b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.BroadcastChannel.sub.https.window.js
index feb268b4b81..feb268b4b81 100644
--- a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.BroadcastChannel.tentative.sub.https.window.js
+++ b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.BroadcastChannel.sub.https.window.js
diff --git a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.SharedWorker.tentative.sub.https.window.js b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.SharedWorker.sub.https.window.js
index 613a47ba1b7..e05c2ef38d1 100644
--- a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.SharedWorker.tentative.sub.https.window.js
+++ b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.SharedWorker.sub.https.window.js
@@ -12,7 +12,6 @@
// Step 6 (sub-sub-frame) Try to use storage access API to access first-party shared worker.
// Step 7 (sub-sub-frame) Send "HasAccess for SharedWorker" message to top-frame.
// Step 8 (top-frame) Set up cookie worker to expect it's already opened.
-// TODO(crbug.com/1484966): Verify access to cookies in shared workers.
async_test(t => {
// Step 1
diff --git a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.blobStorage.tentative.sub.https.window.js b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.blobStorage.sub.https.window.js
index cc2785b6fac..cc2785b6fac 100644
--- a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.blobStorage.tentative.sub.https.window.js
+++ b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.blobStorage.sub.https.window.js
diff --git a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.caches.tentative.sub.https.window.js b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.caches.sub.https.window.js
index 51e5c648a64..51e5c648a64 100644
--- a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.caches.tentative.sub.https.window.js
+++ b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.caches.sub.https.window.js
diff --git a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.cookies.tentative.sub.https.window.js b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.cookies.sub.https.window.js
index ad760cfda7d..ad760cfda7d 100644
--- a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.cookies.tentative.sub.https.window.js
+++ b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.cookies.sub.https.window.js
diff --git a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.estimate.tentative.sub.https.window.js b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.estimate.sub.https.window.js
index eb8cb0c68d0..eb8cb0c68d0 100644
--- a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.estimate.tentative.sub.https.window.js
+++ b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.estimate.sub.https.window.js
diff --git a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.getDirectory.tentative.sub.https.window.js b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.getDirectory.sub.https.window.js
index d59caa93cd6..d59caa93cd6 100644
--- a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.getDirectory.tentative.sub.https.window.js
+++ b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.getDirectory.sub.https.window.js
diff --git a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.indexedDB.tentative.sub.https.window.js b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.indexedDB.sub.https.window.js
index 8e9420da0da..8e9420da0da 100644
--- a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.indexedDB.tentative.sub.https.window.js
+++ b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.indexedDB.sub.https.window.js
diff --git a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.localStorage.tentative.sub.https.window.js b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.localStorage.sub.https.window.js
index 80021317790..80021317790 100644
--- a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.localStorage.tentative.sub.https.window.js
+++ b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.localStorage.sub.https.window.js
diff --git a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.locks.tentative.sub.https.window.js b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.locks.sub.https.window.js
index ed7d6ea4847..ed7d6ea4847 100644
--- a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.locks.tentative.sub.https.window.js
+++ b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.locks.sub.https.window.js
diff --git a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.none.tentative.sub.https.window.js b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.none.sub.https.window.js
index ba5ea3279df..ba5ea3279df 100644
--- a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.none.tentative.sub.https.window.js
+++ b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.none.sub.https.window.js
diff --git a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.sessionStorage.tentative.sub.https.window.js b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.sessionStorage.sub.https.window.js
index 93b243f6c11..93b243f6c11 100644
--- a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.sessionStorage.tentative.sub.https.window.js
+++ b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.sessionStorage.sub.https.window.js
diff --git a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.unpartitioned.tentative.sub.https.window.js b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.unpartitioned.sub.https.window.js
index ddc5b49f481..ddc5b49f481 100644
--- a/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.unpartitioned.tentative.sub.https.window.js
+++ b/tests/wpt/tests/storage-access-api/storage-access-beyond-cookies.unpartitioned.sub.https.window.js
diff --git a/tests/wpt/tests/svg/painting/reftests/non-scaling-stroke-003.html b/tests/wpt/tests/svg/painting/reftests/non-scaling-stroke-003.html
index 147bf814d30..4fec5711d6c 100644
--- a/tests/wpt/tests/svg/painting/reftests/non-scaling-stroke-003.html
+++ b/tests/wpt/tests/svg/painting/reftests/non-scaling-stroke-003.html
@@ -1,6 +1,6 @@
<!DOCTYPE html>
<html>
-<title>non-scaling-stroke with screen transform</title>
+<title>non-scaling-stroke with outer viewport transform</title>
<link rel="help" href="https://svgwg.org/svg2-draft/painting.html#PaintingVectorEffects" />
<link rel="match" href="green-100x100.svg" />
<body>
@@ -10,24 +10,30 @@
margin: 0;
width: 200px;
height: 200px;
- transform: scale(0.5);
}
svg {
- width: 100px;
- height: 100px;
- transform: scale(2) translate(-25px, -25px);
transform-origin: center;
transform-bxox: fill-box;
}
+ #outer {
+ width: 100px;
+ height: 100px;
+ transform: scale(2);
+ }
+ #inner {
+ transform: scale(0.5);
+ }
rect {
fill: red;
stroke: green;
stroke-width: 75px;
- vector-effect: non-scaling-stroke screen;
+ vector-effect: non-scaling-stroke;
}
</style>
- <svg>
- <rect width="75" height="75"/>
+ <svg id="outer">
+ <svg id="inner">
+ <rect width="75" height="75"/>
+ </svg>
</svg>
</body>
</html>
diff --git a/tests/wpt/tests/svg/painting/reftests/symbol-in-mask.html b/tests/wpt/tests/svg/painting/reftests/symbol-in-mask.html
new file mode 100644
index 00000000000..da1e11f1704
--- /dev/null
+++ b/tests/wpt/tests/svg/painting/reftests/symbol-in-mask.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Mask behaviour when mask contains a symbol element</title>
+ <link rel="match" href="green-100x100.svg">
+ <link rel="help" href="https://drafts.fxtf.org/css-masking-1/#MaskElement">
+ <meta name="assert" content="A symbol in a mask should not be displayed">
+</head>
+<style>
+ html, body, svg {
+ padding: 0;
+ margin: 0;
+ }
+</style>
+<body>
+ <svg width="200" height="200">
+ <defs>
+ <mask id="aMask">
+ <symbol>
+ <rect x="0" y="0" width="100" height="100" fill="#ffffff"/>
+ </symbol>
+ </mask>
+ </defs>
+ <rect width="100" height="100" fill="green"/>
+ <foreignObject width="200" height="200" style="mask: url('#aMask');">
+ <div style="width: 200px; height: 200px; background: red;"></div>
+ </foreignObject>
+ </svg>
+</body>
+</html>
diff --git a/tests/wpt/tests/svg/styling/vector-effect-invalid.html b/tests/wpt/tests/svg/styling/vector-effect-invalid.html
index ec49b88c599..18f82502ddd 100644
--- a/tests/wpt/tests/svg/styling/vector-effect-invalid.html
+++ b/tests/wpt/tests/svg/styling/vector-effect-invalid.html
@@ -18,6 +18,9 @@ test_invalid_value("vector-effect", "none screen");
test_invalid_value("vector-effect", "viewport");
test_invalid_value("vector-effect", "screen");
test_invalid_value("vector-effect", "screen non-scaling-stroke");
+// The following were removed by https://github.com/w3c/svgwg/issues/582
+test_invalid_value("vector-effect", "non-scaling-stroke viewport");
+test_invalid_value("vector-effect", "non-scaling-stroke screen");
</script>
</body>
</html>
diff --git a/tests/wpt/tests/svg/styling/vector-effect-valid.html b/tests/wpt/tests/svg/styling/vector-effect-valid.html
deleted file mode 100644
index 9563db0523b..00000000000
--- a/tests/wpt/tests/svg/styling/vector-effect-valid.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<meta charset="utf-8">
-<title>vector-effect test: parsing vector-effect with invalid values</title>
-<link rel="help" href="https://www.w3.org/TR/SVG2/coords.html#VectorEffects">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/css/support/parsing-testcommon.js"></script>
-</head>
-<body>
-<script>
-test_valid_value("vector-effect", "none");
-test_valid_value("vector-effect", "non-scaling-stroke");
-test_valid_value("vector-effect", "non-scaling-stroke viewport", "non-scaling-stroke");
-test_valid_value("vector-effect", "non-scaling-stroke screen");
-</script>
-</body>
-</html>
diff --git a/tests/wpt/tests/tools/ci/requirements_tc.txt b/tests/wpt/tests/tools/ci/requirements_tc.txt
index a9128c02eb9..9bc3c840ab4 100644
--- a/tests/wpt/tests/tools/ci/requirements_tc.txt
+++ b/tests/wpt/tests/tools/ci/requirements_tc.txt
@@ -1,4 +1,4 @@
pygithub==2.3.0
pyyaml==6.0.1
requests==2.32.3
-taskcluster==64.2.8
+taskcluster==66.0.0
diff --git a/tests/wpt/tests/tools/ci/tc/tasks/test.yml b/tests/wpt/tests/tools/ci/tc/tasks/test.yml
index a9ca07c6ceb..fffb146dacc 100644
--- a/tests/wpt/tests/tools/ci/tc/tasks/test.yml
+++ b/tests/wpt/tests/tools/ci/tc/tasks/test.yml
@@ -4,7 +4,7 @@ components:
workerType: ci
schedulerId: taskcluster-github
deadline: "24 hours"
- image: webplatformtests/wpt:0.57
+ image: ghcr.io/web-platform-tests/wpt:1
maxRunTime: 7200
artifacts:
public/results:
diff --git a/tests/wpt/tests/tools/docker/README.md b/tests/wpt/tests/tools/docker/README.md
index bc98d198612..9ec7a99cfff 100644
--- a/tests/wpt/tests/tools/docker/README.md
+++ b/tests/wpt/tests/tools/docker/README.md
@@ -1,16 +1,17 @@
This docker images is used for testing Chrome, Firefox, WebKitGTK and running
other tasks on Taskcluster. When any of the files in this directory change, the
-images must be updated as well. Doing this requires you be part of the
-'webplatformtests' organization on Docker Hub; ping @foolip or @jpchase
-if you are not a member.
+images must be updated as well. Doing this requires you to have write
+permissions to the repository.
The tag for a new docker image is of the form
-`webplatformtests/wpt:{current-version + 0.01}`
+`ghcr.io/web-platform-tests/wpt:${PREV_VERSION++}`.
To update the docker image:
+* Run the workflow
+ https://github.com/web-platform-tests/wpt/actions/workflows/docker.yml via the
+ GitHub UI.
+
* Update the following Taskcluster configurations to use the new image:
- `.taskcluster.yml` (the decision task)
- - `tools/ci/tc/tasks/test.yml` (all the other tasks)
-
-* Run `wpt docker-push`
+ - `tools/ci/tc/tasks/test.yml` (all the other tasks) \ No newline at end of file
diff --git a/tests/wpt/tests/tools/docker/frontend.py b/tests/wpt/tests/tools/docker/frontend.py
index f91e79fa78e..05f4e0fc8db 100644
--- a/tests/wpt/tests/tools/docker/frontend.py
+++ b/tests/wpt/tests/tools/docker/frontend.py
@@ -56,17 +56,10 @@ def read_image_name():
return taskcluster_values, tests_value
-def lookup_tag(tag):
- import requests
- org, repo_version = tag.split("/", 1)
- repo, version = repo_version.rsplit(":", 1)
- resp = requests.get("https://hub.docker.com/v2/repositories/%s/%s/tags/%s" %
- (org, repo, version))
- if resp.status_code == 200:
- return True
- if resp.status_code == 404:
- return False
- resp.raise_for_status()
+def tag_exists(tag):
+ retcode = subprocess.call(["docker", "manifest", "inspect", tag])
+ # The command succeeds if the tag exists.
+ return retcode != 0
def push(venv, tag=None, force=False, *args, **kwargs):
@@ -89,13 +82,13 @@ def push(venv, tag=None, force=False, *args, **kwargs):
logger.info("Using tag %s from .taskcluster.yml" % taskcluster_tag)
tag = taskcluster_tag
- tag_re = re.compile(r"webplatformtests/wpt:\d\.\d+")
+ tag_re = re.compile(r"ghcr.io/web-platform-tests/wpt:\d+")
if not tag_re.match(tag):
- error_log("Tag doesn't match expected format webplatformtests/wpt:0.x")
+ error_log("Tag doesn't match expected format ghcr.io/web-platform-tests/wpt:x")
if not force:
sys.exit(1)
- if lookup_tag(tag):
+ if tag_exists(tag):
# No override for this case
logger.critical("Tag %s already exists" % tag)
sys.exit(1)
diff --git a/tests/wpt/tests/tools/requirements_mypy.txt b/tests/wpt/tests/tools/requirements_mypy.txt
index 2addbba3b22..66ae1420387 100644
--- a/tests/wpt/tests/tools/requirements_mypy.txt
+++ b/tests/wpt/tests/tools/requirements_mypy.txt
@@ -1,4 +1,4 @@
-mypy==1.10.0
+mypy==1.10.1
mypy-extensions==1.0.0
toml==0.10.2
tomli==2.0.1
diff --git a/tests/wpt/tests/tools/requirements_pytest.txt b/tests/wpt/tests/tools/requirements_pytest.txt
index 75d70d49bd8..14cc97014aa 100644
--- a/tests/wpt/tests/tools/requirements_pytest.txt
+++ b/tests/wpt/tests/tools/requirements_pytest.txt
@@ -1,3 +1,3 @@
pytest==8.2.1
pytest-cov==5.0.0
-hypothesis==6.100.2
+hypothesis==6.104.1
diff --git a/tests/wpt/tests/tools/requirements_tests.txt b/tests/wpt/tests/tools/requirements_tests.txt
index 74792e61314..c43af1ba8ba 100644
--- a/tests/wpt/tests/tools/requirements_tests.txt
+++ b/tests/wpt/tests/tools/requirements_tests.txt
@@ -2,5 +2,5 @@ httpx[http2]==0.27.0
json-e==4.7.0
jsonschema==4.17.3
pyyaml==6.0.1
-taskcluster==64.2.8
+taskcluster==66.0.0
mozterm==1.0.0
diff --git a/tests/wpt/tests/tools/webdriver/webdriver/bidi/client.py b/tests/wpt/tests/tools/webdriver/webdriver/bidi/client.py
index ee3ef1ea95d..d4cc78588e9 100644
--- a/tests/wpt/tests/tools/webdriver/webdriver/bidi/client.py
+++ b/tests/wpt/tests/tools/webdriver/webdriver/bidi/client.py
@@ -127,7 +127,7 @@ class BidiSession:
requested_capabilities: Optional[Mapping[str, Any]] = None) -> "BidiSession":
"""Create a BiDi session where there is no existing HTTP session
- :param webdocket_url: URL to the WebSocket server listening for BiDi connections
+ :param websocket_url: URL to the WebSocket server listening for BiDi connections
:param requested_capabilities: Capabilities request for establishing the session."""
return cls(websocket_url, requested_capabilities=requested_capabilities)
diff --git a/tests/wpt/tests/tools/webdriver/webdriver/bidi/modules/network.py b/tests/wpt/tests/tools/webdriver/webdriver/bidi/modules/network.py
index 46c5885271a..dc895d9834c 100644
--- a/tests/wpt/tests/tools/webdriver/webdriver/bidi/modules/network.py
+++ b/tests/wpt/tests/tools/webdriver/webdriver/bidi/modules/network.py
@@ -1,3 +1,4 @@
+from enum import Enum
from typing import Any, Dict, List, Mapping, MutableMapping, Optional, Union
from ._module import BidiModule, command
@@ -8,6 +9,11 @@ class AuthCredentials(Dict[str, Any]):
dict.__init__(self, type="password", username=username, password=password)
+class CacheBehavior(Enum):
+ BYPASS = "bypass"
+ DEFAULT = "default"
+
+
class NetworkBase64Value(Dict[str, Any]):
def __init__(self, value: str):
dict.__init__(self, type="base64", value=value)
@@ -243,10 +249,11 @@ class Network(BidiModule):
return params
@command
- def set_cache_bypass(
- self, bypass: bool, contexts: Optional[List[str]] = None
- ) -> Mapping[str, Any]:
- params: MutableMapping[str, Any] = {"bypass": bypass}
+ def set_cache_behavior(
+ self,
+ cache_behavior: CacheBehavior,
+ contexts: Optional[List[str]] = None) -> Mapping[str, Any]:
+ params: MutableMapping[str, Any] = {"cacheBehavior": cache_behavior}
if contexts is not None:
params["contexts"] = contexts
diff --git a/tests/wpt/tests/tools/webdriver/webdriver/transport.py b/tests/wpt/tests/tools/webdriver/webdriver/transport.py
index ca1ff74ef96..7e898b2869f 100644
--- a/tests/wpt/tests/tools/webdriver/webdriver/transport.py
+++ b/tests/wpt/tests/tools/webdriver/webdriver/transport.py
@@ -224,15 +224,7 @@ class HTTPWireProtocol:
raise ValueError("Failed to encode request body as JSON:\n"
"%s" % json.dumps(body, indent=2))
- # When the timeout triggers, the TestRunnerManager thread will reuse
- # this connection to check if the WebDriver its alive and we may end
- # raising an httplib.CannotSendRequest exception if the WebDriver is
- # not responding and this httplib.request() call is blocked on the
- # runner thread. We use the boolean below to check for that and restart
- # the connection in that case.
- self._last_request_is_blocked = True
response = self._request(method, uri, payload, headers, timeout=None)
- self._last_request_is_blocked = False
return Response.from_http(response, decoder=decoder, **codec_kwargs)
def _request(self, method, uri, payload, headers=None, timeout=None):
@@ -248,6 +240,13 @@ class HTTPWireProtocol:
if self._last_request_is_blocked or self._has_unread_data():
self.close()
+ # When the timeout triggers, the TestRunnerManager thread will reuse
+ # this connection to check if the WebDriver its alive and we may end
+ # raising an httplib.CannotSendRequest exception if the WebDriver is
+ # not responding and this httplib.request() call is blocked on the
+ # runner thread. We use the boolean below to check for that and restart
+ # the connection in that case.
+ self._last_request_is_blocked = True
self.connection.request(method, url, payload, headers)
# timeout for request has to be set just before calling httplib.getresponse()
@@ -261,6 +260,7 @@ class HTTPWireProtocol:
if timeout:
self._conn.settimeout(previous_timeout)
+ self._last_request_is_blocked = False
return response
def _has_unread_data(self):
diff --git a/tests/wpt/tests/tools/wptrunner/wptrunner/testharnessreport-content-shell.js b/tests/wpt/tests/tools/wptrunner/wptrunner/testharnessreport-content-shell.js
deleted file mode 100644
index 1e24fdbcfbf..00000000000
--- a/tests/wpt/tests/tools/wptrunner/wptrunner/testharnessreport-content-shell.js
+++ /dev/null
@@ -1,45 +0,0 @@
-(function() {
- var props = {output:%(output)d, debug: %(debug)s};
- setup(props);
-
- // Some tests navigate away from the original URL as part of the
- // functionality they exercise. In that case, `add_completion_callback(...)`
- // uses the final `window.location` to report the test ID, which may not be
- // correct [1].
- //
- // Persisting the original `window.location` with standard web platform APIs
- // (e.g., `localStorage`) could interfere with the their tests, so this must
- // be avoided. Unfortunately, there doesn't appear to be anything in content
- // shell's protocol mode or Blink-specific `window.testRunner` or
- // `window.internals` [2] that could help with this. As such, the driver
- // simply downgrades a mismatched test ID to a logged warning instead of a
- // harness error.
- //
- // [1] crbug.com/1418753
- // [2] https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/testing/writing_web_tests.md#Relying-on-Blink_Specific-Testing-APIs
- const url = new URL(location.href);
-
- testRunner.dumpAsText();
- testRunner.waitUntilDone();
- testRunner.setPopupBlockingEnabled(false);
- testRunner.setDumpJavaScriptDialogs(false);
- // Show `CONSOLE MESSAGE:` and `CONSOLE ERROR:` in stderr.
- if (props.debug) {
- testRunner.setDumpConsoleMessages(true);
- }
-
- add_completion_callback(function (tests, harness_status) {
- const test_id = decodeURIComponent(url.pathname) + decodeURIComponent(url.search) + decodeURIComponent(url.hash);
- const result_string = JSON.stringify([
- test_id,
- harness_status.status,
- harness_status.message,
- harness_status.stack,
- tests.map(function(t) {
- return [t.name, t.status, t.message, t.stack]
- }),
- ]);
- testRunner.setCustomTextOutput(result_string);
- testRunner.notifyDone();
- });
-})();
diff --git a/tests/wpt/tests/webcodecs/audio-encoder-config.https.any.js b/tests/wpt/tests/webcodecs/audio-encoder-config.https.any.js
index a70bf28f80c..ad5d56ca492 100644
--- a/tests/wpt/tests/webcodecs/audio-encoder-config.https.any.js
+++ b/tests/wpt/tests/webcodecs/audio-encoder-config.https.any.js
@@ -57,6 +57,15 @@ const invalidConfigs = [
},
},
{
+ comment: 'Bit rate present but equal to zero',
+ config: {
+ codec: 'opus',
+ sampleRate: 8000,
+ numberOfChannels: 2,
+ bitrate: 0,
+ },
+ },
+ {
comment: 'Opus complexity too big',
config: {
codec: 'opus',
diff --git a/tests/wpt/tests/webcodecs/full-cycle-test.https.any.js b/tests/wpt/tests/webcodecs/full-cycle-test.https.any.js
index 8e1ae65bdca..a63edd04d75 100644
--- a/tests/wpt/tests/webcodecs/full-cycle-test.https.any.js
+++ b/tests/wpt/tests/webcodecs/full-cycle-test.https.any.js
@@ -149,7 +149,10 @@ async function runFullCycleTest(t, options) {
const encoder_init = {
output(chunk, metadata) {
let config = metadata.decoderConfig;
- if (config) {
+ // Issue a configure if there's a new config, or on the
+ // first chunk if testing rate control
+ if (!options.rateControl && config ||
+ options.rateControl && chunk.timestamp == 0) {
config.hardwareAcceleration = encoder_config.hardwareAcceleration;
encoder_color_space = config.colorSpace;
@@ -185,6 +188,11 @@ async function runFullCycleTest(t, options) {
let keyframe = (i % 5 == 0);
encoder.encode(frame, { keyFrame: keyframe });
+ if (i % 3 == 0 && options.rateControl) {
+ // reconfigure with a different rate
+ encoder_config.bitrate = encoder_config.bitrate * 0.9;
+ encoder.configure(encoder_config);
+ }
frame.close();
}
await encoder.flush();
@@ -211,3 +219,7 @@ promise_test(async t => {
if (ENCODER_CONFIG.hasEmbeddedColorSpace)
return runFullCycleTest(t, {stripDecoderConfigColorSpace: true});
}, 'Encoding and decoding cycle w/ stripped color space');
+
+promise_test(async t => {
+ return runFullCycleTest(t, {rateControl: true});
+}, 'Encoding and decoding cycle w/ rate control');
diff --git a/tests/wpt/tests/webcodecs/video-encoder-config.https.any.js b/tests/wpt/tests/webcodecs/video-encoder-config.https.any.js
index 8d554e4f4db..d61958919d7 100644
--- a/tests/wpt/tests/webcodecs/video-encoder-config.https.any.js
+++ b/tests/wpt/tests/webcodecs/video-encoder-config.https.any.js
@@ -51,6 +51,15 @@ const invalidConfigs = [
height: 480,
},
},
+ {
+ comment: 'bitrate is present but zero',
+ config: {
+ codec: 'vp8',
+ width: 640,
+ height: 480,
+ bitrate: 0
+ },
+ },
];
invalidConfigs.forEach(entry => {
diff --git a/tests/wpt/tests/webcodecs/videoFrame-copyTo-rgb.any.js b/tests/wpt/tests/webcodecs/videoFrame-copyTo-rgb.any.js
index 146b6756cb4..eb7351e048b 100644
--- a/tests/wpt/tests/webcodecs/videoFrame-copyTo-rgb.any.js
+++ b/tests/wpt/tests/webcodecs/videoFrame-copyTo-rgb.any.js
@@ -2,6 +2,19 @@
// META: script=/webcodecs/videoFrame-utils.js
// META: script=/webcodecs/video-encoder-utils.js
+const smpte170m = {
+ matrix: 'smpte170m',
+ primaries: 'smpte170m',
+ transfer: 'smpte170m',
+ fullRange: false
+};
+const bt709 = {
+ matrix: 'bt709',
+ primaries: 'bt709',
+ transfer: 'bt709',
+ fullRange: false
+};
+
function compareColors(actual, expected, tolerance, msg) {
let channel = ['R', 'G', 'B', 'A'];
for (let i = 0; i < 4; i++) {
@@ -24,7 +37,7 @@ function rgb2yuv(r, g, b) {
}
}
-function makeI420Frames() {
+function makeI420Frames(colorSpace) {
const kYellow = {r: 0xFF, g: 0xFF, b: 0x00};
const kRed = {r: 0xFF, g: 0x00, b: 0x00};
const kBlue = {r: 0x00, g: 0x00, b: 0xFF};
@@ -33,68 +46,39 @@ function makeI420Frames() {
const kMagenta = {r: 0xFF, g: 0x00, b: 0xFF};
const kBlack = {r: 0x00, g: 0x00, b: 0x00};
const kWhite = {r: 0xFF, g: 0xFF, b: 0xFF};
- const smpte170m = {
- matrix: 'smpte170m',
- primaries: 'smpte170m',
- transfer: 'smpte170m',
- fullRange: false
- };
- const bt709 = {
- matrix: 'bt709',
- primaries: 'bt709',
- transfer: 'bt709',
- fullRange: false
- };
const result = [];
const init = {format: 'I420', timestamp: 0, codedWidth: 4, codedHeight: 4};
const colors =
[kYellow, kRed, kBlue, kGreen, kMagenta, kBlack, kWhite, kPink];
const data = new Uint8Array(24);
- for (let colorSpace of [null, smpte170m, bt709]) {
- init.colorSpace = colorSpace;
+ init.colorSpace = colorSpace;
+ for (let color of colors) {
+ color = rgb2yuv(color.r, color.g, color.b);
+ data.fill(color.y, 0, 16);
+ data.fill(color.u, 16, 20);
+ data.fill(color.v, 20, 24);
result.push(new VideoFrame(data, init));
- for (let color of colors) {
- color = rgb2yuv(color.r, color.g, color.b);
- data.fill(color.y, 0, 16);
- data.fill(color.u, 16, 20);
- data.fill(color.v, 20, 24);
- result.push(new VideoFrame(data, init));
- }
}
return result;
}
-function makeRGBXFrames() {
+function makeRGBXFrames(colorSpace) {
const kYellow = 0xFFFF00;
const kRed = 0xFF0000;
const kBlue = 0x0000FF;
const kGreen = 0x00FF00;
const kBlack = 0x000000;
const kWhite = 0xFFFFFF;
- const smpte170m = {
- matrix: 'smpte170m',
- primaries: 'smpte170m',
- transfer: 'smpte170m',
- fullRange: false
- };
- const bt709 = {
- matrix: 'bt709',
- primaries: 'bt709',
- transfer: 'bt709',
- fullRange: false
- };
const result = [];
const init = {format: 'RGBX', timestamp: 0, codedWidth: 4, codedHeight: 4};
const colors = [kYellow, kRed, kBlue, kGreen, kBlack, kWhite];
const data = new Uint32Array(16);
- for (let colorSpace of [null, smpte170m, bt709]) {
- init.colorSpace = colorSpace;
- for (let color of colors) {
- data.fill(color, 0, 16);
- result.push(new VideoFrame(data, init));
- }
+ init.colorSpace = colorSpace;
+ for (let color of colors) {
+ data.fill(color, 0, 16);
+ result.push(new VideoFrame(data, init));
}
return result;
}
@@ -156,12 +140,15 @@ async function testFrame(frame, colorSpace, pixelFormat) {
function test_4x4_I420_frames() {
for (let colorSpace of ['srgb', 'display-p3']) {
for (let pixelFormat of ['RGBA', 'RGBX', 'BGRA', 'BGRX']) {
- promise_test(async t => {
- for (let frame of makeI420Frames()) {
- await testFrame(frame, colorSpace, pixelFormat);
- frame.close();
- }
- }, `Convert 4x4 I420 frames to ${pixelFormat} / ${colorSpace}`);
+ for (let frameColorSpace of [null, smpte170m, bt709]) {
+ const frameColorSpaceName = frameColorSpace? frameColorSpace.primaries : "null";
+ promise_test(async t => {
+ for (let frame of makeI420Frames(frameColorSpace)) {
+ await testFrame(frame, colorSpace, pixelFormat);
+ frame.close();
+ }
+ }, `Convert 4x4 ${frameColorSpaceName} I420 frames to ${pixelFormat} / ${colorSpace}`);
+ }
}
}
}
@@ -170,12 +157,15 @@ test_4x4_I420_frames();
function test_4x4_RGB_frames() {
for (let colorSpace of ['srgb', 'display-p3']) {
for (let pixelFormat of ['RGBA', 'RGBX', 'BGRA', 'BGRX']) {
- promise_test(async t => {
- for (let frame of makeRGBXFrames()) {
- await testFrame(frame, colorSpace, pixelFormat);
- frame.close();
- }
- }, `Convert 4x4 RGBX frames to ${pixelFormat} / ${colorSpace}`);
+ for (let frameColorSpace of [null, smpte170m, bt709]) {
+ const frameColorSpaceName = frameColorSpace? frameColorSpace.primaries : "null";
+ promise_test(async t => {
+ for (let frame of makeRGBXFrames(frameColorSpace)) {
+ await testFrame(frame, colorSpace, pixelFormat);
+ frame.close();
+ }
+ }, `Convert 4x4 ${frameColorSpaceName} RGBX frames to ${pixelFormat} / ${colorSpace}`);
+ }
}
}
}
diff --git a/tests/wpt/tests/webdriver/tests/bidi/browsing_context/close/prompt_unload.py b/tests/wpt/tests/webdriver/tests/bidi/browsing_context/close/prompt_unload.py
index 2dc241ba396..e7d88ad272d 100644
--- a/tests/wpt/tests/webdriver/tests/bidi/browsing_context/close/prompt_unload.py
+++ b/tests/wpt/tests/webdriver/tests/bidi/browsing_context/close/prompt_unload.py
@@ -51,7 +51,7 @@ async def test_prompt_unload_not_triggering_dialog(
remove_listener()
-@pytest.mark.capabilities({"unhandledPromptBehavior": {'default': 'ignore'}})
+@pytest.mark.capabilities({"unhandledPromptBehavior": {'beforeUnload': 'ignore'}})
@pytest.mark.parametrize("type_hint", ["window", "tab"])
async def test_prompt_unload_triggering_dialog(
bidi_session,
diff --git a/tests/wpt/tests/webdriver/tests/bidi/browsing_context/handle_user_prompt/handle_user_prompt.py b/tests/wpt/tests/webdriver/tests/bidi/browsing_context/handle_user_prompt/handle_user_prompt.py
index 5bbc616960f..9044f220bcc 100644
--- a/tests/wpt/tests/webdriver/tests/bidi/browsing_context/handle_user_prompt/handle_user_prompt.py
+++ b/tests/wpt/tests/webdriver/tests/bidi/browsing_context/handle_user_prompt/handle_user_prompt.py
@@ -112,7 +112,7 @@ async def test_prompt(
assert result == {"type": "null"}
-@pytest.mark.capabilities({"unhandledPromptBehavior": {'default': 'ignore'}})
+@pytest.mark.capabilities({"unhandledPromptBehavior": {'beforeUnload': 'ignore'}})
@pytest.mark.parametrize("accept", [True, False])
async def test_beforeunload(
bidi_session,
diff --git a/tests/wpt/tests/webdriver/tests/bidi/browsing_context/user_prompt_closed/beforeunload.py b/tests/wpt/tests/webdriver/tests/bidi/browsing_context/user_prompt_closed/beforeunload.py
index 6b2e788a058..ab16526fdb1 100644
--- a/tests/wpt/tests/webdriver/tests/bidi/browsing_context/user_prompt_closed/beforeunload.py
+++ b/tests/wpt/tests/webdriver/tests/bidi/browsing_context/user_prompt_closed/beforeunload.py
@@ -9,7 +9,7 @@ USER_PROMPT_CLOSED_EVENT = "browsingContext.userPromptClosed"
USER_PROMPT_OPENED_EVENT = "browsingContext.userPromptOpened"
-@pytest.mark.capabilities({"unhandledPromptBehavior": {'default': 'ignore'}})
+@pytest.mark.capabilities({"unhandledPromptBehavior": {'beforeUnload': 'ignore'}})
@pytest.mark.parametrize("accept", [False, True])
async def test_beforeunload(
bidi_session,
diff --git a/tests/wpt/tests/webdriver/tests/bidi/browsing_context/user_prompt_opened/user_prompt_opened.py b/tests/wpt/tests/webdriver/tests/bidi/browsing_context/user_prompt_opened/user_prompt_opened.py
index 3a995b15038..c24128004f7 100644
--- a/tests/wpt/tests/webdriver/tests/bidi/browsing_context/user_prompt_opened/user_prompt_opened.py
+++ b/tests/wpt/tests/webdriver/tests/bidi/browsing_context/user_prompt_opened/user_prompt_opened.py
@@ -202,3 +202,69 @@ async def test_iframe(
"handler": "dismiss",
"message": "in iframe",
}
+
+
+@pytest.mark.parametrize("type_hint", ["tab", "window"])
+async def test_two_prompts(
+ bidi_session,
+ subscribe_events,
+ inline,
+ wait_for_event,
+ wait_for_future_safe,
+ type_hint,
+):
+ new_context = await bidi_session.browsing_context.create(type_hint=type_hint)
+ await subscribe_events(
+ events=[USER_PROMPT_OPENED_EVENT]
+ )
+ # Track all received browsingContext.userPromptOpened events in the events array
+ events = []
+
+ async def on_event(method, data):
+ events.append(data)
+
+ remove_listener = bidi_session.add_event_listener(
+ USER_PROMPT_OPENED_EVENT, on_event
+ )
+
+ on_first_event = wait_for_event(USER_PROMPT_OPENED_EVENT)
+
+ another_new_context = await bidi_session.browsing_context.create(
+ type_hint=type_hint
+ )
+
+ # Open a prompt in the first context.
+ await bidi_session.browsing_context.navigate(
+ context=new_context["context"],
+ url=inline("<script>window.alert('first tab')</script>"),
+ )
+
+ await wait_for_future_safe(on_first_event)
+
+ # Open a prompt in the second context.
+ await bidi_session.browsing_context.navigate(
+ context=another_new_context["context"],
+ url=inline("<script>window.confirm('second tab')</script>"),
+ )
+
+ on_second_event = wait_for_event(USER_PROMPT_OPENED_EVENT)
+
+ await wait_for_future_safe(on_second_event)
+
+ assert len(events) == 2
+
+ assert events == [{
+ "context": new_context["context"],
+ "type": "alert",
+ "handler": "dismiss",
+ "message": "first tab",
+ }, {
+ "context": another_new_context["context"],
+ "type": "confirm",
+ "handler": "dismiss",
+ "message": "second tab",
+ }]
+
+ remove_listener()
+ await bidi_session.browsing_context.close(context=new_context["context"])
+ await bidi_session.browsing_context.close(context=another_new_context["context"])
diff --git a/tests/wpt/tests/webdriver/tests/bidi/integration/cookies_with_network_events.py b/tests/wpt/tests/webdriver/tests/bidi/integration/cookies_with_network_events.py
index 16ca358e23b..30ba8e3bd7b 100644
--- a/tests/wpt/tests/webdriver/tests/bidi/integration/cookies_with_network_events.py
+++ b/tests/wpt/tests/webdriver/tests/bidi/integration/cookies_with_network_events.py
@@ -118,7 +118,7 @@ async def test_fetch(
# Navigate away from about:blank to make sure document.cookies can be used.
await bidi_session.browsing_context.navigate(
context=new_tab["context"],
- url=url("/webdriver/tests/bidi/support/empty.html"),
+ url=url("/webdriver/tests/bidi/network/support/empty.html"),
wait="complete"
)
diff --git a/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_bypass/__init__.py b/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_behavior/__init__.py
index e69de29bb2d..e69de29bb2d 100644
--- a/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_bypass/__init__.py
+++ b/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_behavior/__init__.py
diff --git a/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_behavior/conftest.py b/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_behavior/conftest.py
new file mode 100644
index 00000000000..1f12c9fcb84
--- /dev/null
+++ b/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_behavior/conftest.py
@@ -0,0 +1,33 @@
+import pytest_asyncio
+import random
+
+from .. import RESPONSE_COMPLETED_EVENT
+
+
+@pytest_asyncio.fixture
+async def is_request_from_cache(
+ wait_for_event, fetch, wait_for_future_safe, top_context
+):
+ async def is_request_from_cache(url, context=top_context):
+ on_response_completed = wait_for_event(RESPONSE_COMPLETED_EVENT)
+ await fetch(url, context=context)
+ event = await wait_for_future_safe(on_response_completed)
+
+ return event["response"]["fromCache"]
+
+ return is_request_from_cache
+
+
+@pytest_asyncio.fixture
+async def is_cache_enabled_for_context(fetch, top_context, url, is_request_from_cache):
+ async def is_cache_enabled_for_context(context=top_context):
+ cached_url = url(
+ f"/webdriver/tests/support/http_handlers/cached.py?status=200&nocache={random.random()}"
+ )
+
+ # Make first request to fill up the cache.
+ await is_request_from_cache(url=cached_url, context=context)
+
+ return await is_request_from_cache(url=cached_url, context=context)
+
+ return is_cache_enabled_for_context
diff --git a/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_behavior/contexts.py b/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_behavior/contexts.py
new file mode 100644
index 00000000000..5872a7d12e0
--- /dev/null
+++ b/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_behavior/contexts.py
@@ -0,0 +1,204 @@
+import pytest
+
+from .. import RESPONSE_COMPLETED_EVENT
+
+pytestmark = pytest.mark.asyncio
+
+
+async def test_one_context(
+ bidi_session,
+ setup_network_test,
+ top_context,
+ new_tab,
+ inline,
+ is_cache_enabled_for_context,
+):
+ await bidi_session.browsing_context.navigate(
+ context=new_tab["context"],
+ url=inline("foo"),
+ wait="complete",
+ )
+
+ await setup_network_test(
+ events=[RESPONSE_COMPLETED_EVENT],
+ contexts=[top_context["context"], new_tab["context"]],
+ )
+
+ # Make sure that cache is enabled by default.
+ assert await is_cache_enabled_for_context(top_context) is True
+ assert await is_cache_enabled_for_context(new_tab) is True
+
+ # Disable cache only in one context.
+ await bidi_session.network.set_cache_behavior(
+ cache_behavior="bypass", contexts=[new_tab["context"]]
+ )
+
+ assert await is_cache_enabled_for_context(top_context) is True
+ assert await is_cache_enabled_for_context(new_tab) is False
+
+ # Reset to default behavior.
+ await bidi_session.network.set_cache_behavior(
+ cache_behavior="default", contexts=[new_tab["context"]]
+ )
+
+
+@pytest.mark.parametrize("type_hint", ["tab", "window"])
+async def test_new_context(
+ bidi_session,
+ setup_network_test,
+ top_context,
+ inline,
+ is_cache_enabled_for_context,
+ type_hint,
+):
+ await setup_network_test(events=[RESPONSE_COMPLETED_EVENT])
+
+ # Make sure that cache is enabled by default.
+ assert await is_cache_enabled_for_context() is True
+
+ # Disable cache only in one context.
+ await bidi_session.network.set_cache_behavior(
+ cache_behavior="bypass", contexts=[top_context["context"]]
+ )
+
+ assert await is_cache_enabled_for_context() is False
+
+ # Create a new tab.
+ new_context = await bidi_session.browsing_context.create(type_hint=type_hint)
+
+ await bidi_session.browsing_context.navigate(
+ context=new_context["context"],
+ url=inline("<div>foo</div>"),
+ wait="complete",
+ )
+
+ # Make sure that the new context still has cache enabled.
+ assert await is_cache_enabled_for_context(context=new_context) is True
+
+ # Reset to default behavior.
+ await bidi_session.network.set_cache_behavior(
+ cache_behavior="default", contexts=[top_context["context"]]
+ )
+
+
+async def test_disable_globally_after_disable_for_context(
+ bidi_session,
+ setup_network_test,
+ top_context,
+ new_tab,
+ inline,
+ is_cache_enabled_for_context,
+):
+ await bidi_session.browsing_context.navigate(
+ context=new_tab["context"],
+ url=inline("foo"),
+ wait="complete",
+ )
+
+ await setup_network_test(
+ events=[RESPONSE_COMPLETED_EVENT],
+ contexts=[top_context["context"], new_tab["context"]],
+ )
+
+ # Make sure that cache is enabled by default.
+ assert await is_cache_enabled_for_context() is True
+ assert await is_cache_enabled_for_context(context=new_tab) is True
+
+ # Disable cache only in one context.
+ await bidi_session.network.set_cache_behavior(
+ cache_behavior="bypass", contexts=[new_tab["context"]]
+ )
+
+ assert await is_cache_enabled_for_context() is True
+ assert await is_cache_enabled_for_context(context=new_tab) is False
+
+ # Disable cache globally.
+ await bidi_session.network.set_cache_behavior(cache_behavior="bypass")
+
+ # Make sure that cache is disabled for both contexts.
+ assert await is_cache_enabled_for_context() is False
+ assert await is_cache_enabled_for_context(context=new_tab) is False
+
+ # Reset to default behavior.
+ await bidi_session.network.set_cache_behavior(cache_behavior="default")
+
+
+async def test_enable_globally_after_disable_for_context(
+ bidi_session,
+ setup_network_test,
+ top_context,
+ new_tab,
+ inline,
+ is_cache_enabled_for_context,
+):
+ await bidi_session.browsing_context.navigate(
+ context=new_tab["context"],
+ url=inline("foo"),
+ wait="complete",
+ )
+
+ await setup_network_test(
+ events=[RESPONSE_COMPLETED_EVENT],
+ contexts=[top_context["context"], new_tab["context"]],
+ )
+
+ # Make sure that cache is enabled by default.
+ assert await is_cache_enabled_for_context(context=top_context) is True
+ assert await is_cache_enabled_for_context(context=new_tab) is True
+
+ # Disable cache only in one context.
+ await bidi_session.network.set_cache_behavior(
+ cache_behavior="bypass", contexts=[new_tab["context"]]
+ )
+
+ assert await is_cache_enabled_for_context(context=top_context) is True
+ assert await is_cache_enabled_for_context(context=new_tab) is False
+
+ # Enable cache globally.
+ await bidi_session.network.set_cache_behavior(cache_behavior="default")
+
+ # Make sure that cache is enabled for both contexts.
+ assert await is_cache_enabled_for_context(context=top_context) is True
+ assert await is_cache_enabled_for_context(context=new_tab) is True
+
+
+async def test_setting_cache_to_contexts_after_global_update(
+ bidi_session,
+ setup_network_test,
+ top_context,
+ new_tab,
+ inline,
+ is_cache_enabled_for_context
+):
+ await bidi_session.browsing_context.navigate(
+ context=new_tab["context"],
+ url=inline("foo"),
+ wait="complete",
+ )
+
+ await setup_network_test(
+ events=[RESPONSE_COMPLETED_EVENT],
+ contexts=[top_context["context"], new_tab["context"]],
+ )
+
+ # Make sure that cache is enabled by default.
+ assert await is_cache_enabled_for_context(context=top_context) is True
+ assert await is_cache_enabled_for_context(context=new_tab) is True
+
+ # Disable cache globally.
+ await bidi_session.network.set_cache_behavior(cache_behavior="bypass")
+
+ assert await is_cache_enabled_for_context(context=top_context) is False
+ assert await is_cache_enabled_for_context(context=new_tab) is False
+
+ # Enable cache for one context.
+ await bidi_session.network.set_cache_behavior(
+ cache_behavior="default", contexts=[new_tab["context"]]
+ )
+
+ # Make sure that cache is disabled only for one context.
+ assert await is_cache_enabled_for_context(context=top_context) is False
+ assert await is_cache_enabled_for_context(context=new_tab) is True
+
+ # Reset to default behavior.
+ await bidi_session.network.set_cache_behavior(cache_behavior="default")
diff --git a/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_behavior/invalid.py b/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_behavior/invalid.py
new file mode 100644
index 00000000000..921db569d4f
--- /dev/null
+++ b/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_behavior/invalid.py
@@ -0,0 +1,39 @@
+import pytest
+import webdriver.bidi.error as error
+
+
+pytestmark = pytest.mark.asyncio
+
+
+@pytest.mark.parametrize("value", [None, False, 42, {}, []])
+async def test_params_cache_behavior_invalid_type(bidi_session, value):
+ with pytest.raises(error.InvalidArgumentException):
+ await bidi_session.network.set_cache_behavior(cache_behavior=value)
+
+
+@pytest.mark.parametrize("value", ["bypas", "DEFAULT"])
+async def test_params_cache_behavior_invalid_value(bidi_session, value):
+ with pytest.raises(error.InvalidArgumentException):
+ await bidi_session.network.set_cache_behavior(cache_behavior=value)
+
+
+@pytest.mark.parametrize("value", ["foo", 42, False, {}])
+async def test_params_contexts_invalid_type(bidi_session, value):
+ with pytest.raises(error.InvalidArgumentException):
+ await bidi_session.network.set_cache_behavior(cache_behavior="bypass", contexts=value)
+
+
+async def test_params_contexts_invalid_value_empty_array(bidi_session):
+ with pytest.raises(error.InvalidArgumentException):
+ await bidi_session.network.set_cache_behavior(cache_behavior="bypass", contexts=[])
+
+
+@pytest.mark.parametrize("value", [None, 42, False, {}, []])
+async def test_params_contexts_invalid_array_element_type(bidi_session, value):
+ with pytest.raises(error.InvalidArgumentException):
+ await bidi_session.network.set_cache_behavior(cache_behavior="bypass", contexts=[value])
+
+
+async def test_params_contexts_invalid_array_element_value(bidi_session):
+ with pytest.raises(error.NoSuchFrameException):
+ await bidi_session.network.set_cache_behavior(cache_behavior="bypass", contexts=["foo"])
diff --git a/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_behavior/set_cache_behavior.py b/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_behavior/set_cache_behavior.py
new file mode 100644
index 00000000000..0d55d520c96
--- /dev/null
+++ b/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_behavior/set_cache_behavior.py
@@ -0,0 +1,51 @@
+import pytest
+
+from .. import RESPONSE_COMPLETED_EVENT
+
+pytestmark = pytest.mark.asyncio
+
+
+async def test_set_cache_behavior(
+ bidi_session, setup_network_test, url, is_cache_enabled_for_context
+):
+ await setup_network_test(events=[RESPONSE_COMPLETED_EVENT])
+
+ # Make sure that cache is enabled by default.
+ assert await is_cache_enabled_for_context() is True
+
+ await bidi_session.network.set_cache_behavior(cache_behavior="bypass")
+
+ assert await is_cache_enabled_for_context() is False
+
+ await bidi_session.network.set_cache_behavior(cache_behavior="default")
+
+ assert await is_cache_enabled_for_context() is True
+
+
+@pytest.mark.parametrize("type_hint", ["tab", "window"])
+async def test_new_context(
+ bidi_session, setup_network_test, inline, is_cache_enabled_for_context, type_hint
+):
+ await setup_network_test(events=[RESPONSE_COMPLETED_EVENT])
+
+ # Make sure that cache is enabled by default.
+ assert await is_cache_enabled_for_context() is True
+
+ await bidi_session.network.set_cache_behavior(cache_behavior="bypass")
+
+ assert await is_cache_enabled_for_context() is False
+
+ # Create a new tab.
+ new_context = await bidi_session.browsing_context.create(type_hint=type_hint)
+
+ await bidi_session.browsing_context.navigate(
+ context=new_context["context"],
+ url=inline("<div>foo</div>"),
+ wait="complete",
+ )
+
+ # Make sure that the new context still has cache disabled.
+ assert await is_cache_enabled_for_context(new_context) is False
+
+ # Reset to default behavior.
+ await bidi_session.network.set_cache_behavior(cache_behavior="default")
diff --git a/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_bypass/conftest.py b/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_bypass/conftest.py
deleted file mode 100644
index 969641b8fd2..00000000000
--- a/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_bypass/conftest.py
+++ /dev/null
@@ -1,17 +0,0 @@
-import pytest_asyncio
-
-from .. import RESPONSE_COMPLETED_EVENT
-
-
-@pytest_asyncio.fixture
-async def is_request_from_cache(
- wait_for_event, fetch, wait_for_future_safe, top_context
-):
- async def is_request_from_cache(url, context=top_context):
- on_response_completed = wait_for_event(RESPONSE_COMPLETED_EVENT)
- await fetch(url, context=context)
- event = await wait_for_future_safe(on_response_completed)
-
- return event["response"]["fromCache"]
-
- return is_request_from_cache
diff --git a/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_bypass/contexts_tentative.py b/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_bypass/contexts_tentative.py
deleted file mode 100644
index 946d6d9a7eb..00000000000
--- a/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_bypass/contexts_tentative.py
+++ /dev/null
@@ -1,99 +0,0 @@
-import pytest
-import random
-
-from .. import RESPONSE_COMPLETED_EVENT
-
-pytestmark = pytest.mark.asyncio
-
-
-async def test_one_context(
- bidi_session,
- setup_network_test,
- top_context,
- new_tab,
- url,
- inline,
- is_request_from_cache,
-):
- await bidi_session.browsing_context.navigate(
- context=new_tab["context"],
- url=inline("foo"),
- wait="complete",
- )
-
- await setup_network_test(
- events=[RESPONSE_COMPLETED_EVENT],
- contexts=[top_context["context"], new_tab["context"]],
- )
-
- cached_url = url(
- f"/webdriver/tests/support/http_handlers/cached.py?status=200&nocache={random.random()}"
- )
-
- # The first request/response is used to fill the browser cache,
- # so we expect fromCache to be False here.
- assert await is_request_from_cache(url=cached_url, context=top_context) is False
-
- # In the second tab it will request from cache.
- assert await is_request_from_cache(url=cached_url, context=new_tab) is True
-
- # Disable cache only in one context.
- await bidi_session.network.set_cache_bypass(
- bypass=True, contexts=[new_tab["context"]]
- )
-
- assert await is_request_from_cache(url=cached_url, context=top_context) is True
- assert await is_request_from_cache(url=cached_url, context=new_tab) is False
-
- # Reset to default behavior.
- await bidi_session.network.set_cache_bypass(
- bypass=False, contexts=[new_tab["context"]]
- )
-
-
-@pytest.mark.parametrize("type_hint", ["tab", "window"])
-async def test_new_context(
- bidi_session,
- setup_network_test,
- top_context,
- url,
- inline,
- is_request_from_cache,
- type_hint,
-):
- await setup_network_test(events=[RESPONSE_COMPLETED_EVENT])
-
- cached_url = url(
- f"/webdriver/tests/support/http_handlers/cached.py?status=200&nocache={random.random()}"
- )
-
- # The first request/response is used to fill the browser cache,
- # so we expect fromCache to be False here.
- assert await is_request_from_cache(url=cached_url) is False
-
- # In the second tab it will request from cache.
- assert await is_request_from_cache(url=cached_url) is True
-
- # Disable cache only in one context.
- await bidi_session.network.set_cache_bypass(
- bypass=True, contexts=[top_context["context"]]
- )
-
- assert await is_request_from_cache(url=cached_url, context=top_context) is False
-
- # Create a new tab.
- new_context = await bidi_session.browsing_context.create(type_hint=type_hint)
-
- await bidi_session.browsing_context.navigate(
- context=new_context["context"],
- url=inline("<div>foo</div>"),
- wait="complete",
- )
-
- # Make sure that the new context still has cache enabled.
- assert await is_request_from_cache(cached_url, context=new_context) is True
-
- # Reset to default behavior.
- await bidi_session.network.set_cache_bypass(
- bypass=False, contexts=[top_context["context"]]
- )
diff --git a/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_bypass/invalid_tentative.py b/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_bypass/invalid_tentative.py
deleted file mode 100644
index 678d5d313f8..00000000000
--- a/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_bypass/invalid_tentative.py
+++ /dev/null
@@ -1,32 +0,0 @@
-import pytest
-import webdriver.bidi.error as error
-
-pytestmark = pytest.mark.asyncio
-
-
-@pytest.mark.parametrize("value", [None, "foo", 42, {}, []])
-async def test_params_bypass_invalid_type(bidi_session, value):
- with pytest.raises(error.InvalidArgumentException):
- await bidi_session.network.set_cache_bypass(bypass=value)
-
-
-@pytest.mark.parametrize("value", ["foo", 42, False, {}])
-async def test_params_contexts_invalid_type(bidi_session, value):
- with pytest.raises(error.InvalidArgumentException):
- await bidi_session.network.set_cache_bypass(bypass=True, contexts=value)
-
-
-async def test_params_contexts_invalid_value_empty_array(bidi_session):
- with pytest.raises(error.InvalidArgumentException):
- await bidi_session.network.set_cache_bypass(bypass=True, contexts=[])
-
-
-@pytest.mark.parametrize("value", [None, 42, False, {}, []])
-async def test_params_contexts_invalid_array_element_type(bidi_session, value):
- with pytest.raises(error.InvalidArgumentException):
- await bidi_session.network.set_cache_bypass(bypass=True, contexts=[value])
-
-
-async def test_params_contexts_invalid_array_element_value(bidi_session):
- with pytest.raises(error.NoSuchFrameException):
- await bidi_session.network.set_cache_bypass(bypass=True, contexts=["foo"])
diff --git a/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_bypass/set_cache_bypass_tentative.py b/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_bypass/set_cache_bypass_tentative.py
deleted file mode 100644
index 42166ef253b..00000000000
--- a/tests/wpt/tests/webdriver/tests/bidi/network/set_cache_bypass/set_cache_bypass_tentative.py
+++ /dev/null
@@ -1,68 +0,0 @@
-import pytest
-import random
-
-from .. import RESPONSE_COMPLETED_EVENT
-
-pytestmark = pytest.mark.asyncio
-
-
-async def test_set_cache_bypass(
- bidi_session, setup_network_test, url, is_request_from_cache
-):
- await setup_network_test(events=[RESPONSE_COMPLETED_EVENT])
-
- cached_url = url(
- f"/webdriver/tests/support/http_handlers/cached.py?status=200&nocache={random.random()}"
- )
-
- # The first request/response is used to fill the browser cache,
- # so we expect fromCache to be False here.
- assert await is_request_from_cache(cached_url) is False
-
- # The second request for the same URL has to be read from the local cache.
- assert await is_request_from_cache(cached_url) is True
-
- await bidi_session.network.set_cache_bypass(bypass=True)
-
- assert await is_request_from_cache(cached_url) is False
-
- await bidi_session.network.set_cache_bypass(bypass=False)
-
- assert await is_request_from_cache(cached_url) is True
-
-
-@pytest.mark.parametrize("type_hint", ["tab", "window"])
-async def test_new_context(
- bidi_session, setup_network_test, url, inline, is_request_from_cache, type_hint
-):
- await setup_network_test(events=[RESPONSE_COMPLETED_EVENT])
-
- cached_url = url(
- f"/webdriver/tests/support/http_handlers/cached.py?status=200&nocache={random.random()}"
- )
-
- # The first request/response is used to fill the browser cache,
- # so we expect fromCache to be False here.
- assert await is_request_from_cache(cached_url) is False
-
- # The second request for the same URL has to be read from the local cache.
- assert await is_request_from_cache(cached_url) is True
-
- await bidi_session.network.set_cache_bypass(bypass=True)
-
- assert await is_request_from_cache(cached_url) is False
-
- # Create a new tab.
- new_context = await bidi_session.browsing_context.create(type_hint=type_hint)
-
- await bidi_session.browsing_context.navigate(
- context=new_context["context"],
- url=inline("<div>foo</div>"),
- wait="complete",
- )
-
- # Make sure that the new context still has cache disabled.
- assert await is_request_from_cache(cached_url, context=new_context) is False
-
- # Reset to default behavior.
- await bidi_session.network.set_cache_bypass(bypass=False)
diff --git a/tests/wpt/tests/webdriver/tests/classic/new_session/unhandled_prompt_behavior.py b/tests/wpt/tests/webdriver/tests/classic/new_session/unhandled_prompt_behavior.py
index 2df69b131ff..05b0f930916 100644
--- a/tests/wpt/tests/webdriver/tests/classic/new_session/unhandled_prompt_behavior.py
+++ b/tests/wpt/tests/webdriver/tests/classic/new_session/unhandled_prompt_behavior.py
@@ -90,13 +90,13 @@ def test_unhandled_prompt_behavior_as_string(
[
( # Check the default behavior with no handlers defined
{},
- "dismiss and notify",
+ {},
True,
True,
),
( # Check the default behavior with a custom value defined
{"default": "accept"},
- "accept",
+ {"default": "accept"},
True,
False,
),
@@ -155,11 +155,7 @@ def test_unhandled_prompt_behavior_as_object(
}
)
value = assert_success(response)
- if prompt == "default":
- # For a single default handler the capability is serialized as a string
- assert value["capabilities"]["unhandledPromptBehavior"] == handler
- else:
- assert value["capabilities"]["unhandledPromptBehavior"] == {prompt: handler}
+ assert value["capabilities"]["unhandledPromptBehavior"] == {prompt: handler}
@pytest.mark.parametrize("handler", PROMPT_HANDLERS)
diff --git a/tests/wpt/tests/webnn/resources/test_data/conv_transpose2d.json b/tests/wpt/tests/webnn/resources/test_data/conv_transpose2d.json
index d15c4a84615..ba25e04094c 100644
--- a/tests/wpt/tests/webnn/resources/test_data/conv_transpose2d.json
+++ b/tests/wpt/tests/webnn/resources/test_data/conv_transpose2d.json
@@ -297,6 +297,55 @@
}
},
{
+ "name": "convTranspose2d options.padding is the same upper padding",
+ "inputs": {
+ "input": {
+ "shape": [1, 3, 3, 1],
+ "data": [
+ 0.5, 0.5, 0.5,
+ 0.5, 0.5, 0.5,
+ 0.5, 0.5, 0.5
+ ],
+ "type": "float32"
+ },
+ "filter": {
+ "shape": [2, 3, 3, 1],
+ "data": [
+ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0
+ ],
+ "type": "float32",
+ "constant": true
+ }
+ },
+ "options": {
+ "outputSizes": [6, 6],
+ "groups": 1,
+ "strides": [2, 2],
+ "dilations": [1, 1],
+ "padding": [0, 1, 0, 1],
+ "filterLayout": "ohwi",
+ "inputLayout": "nhwc"
+ },
+ "expected": {
+ "shape": [1, 6, 6, 2],
+ "data": [
+ 0.5, 0.5, 0.5, 0.5, 1.0, 1.0, 0.5,
+ 0.5, 1.0, 1.0, 0.5, 0.5, 0.5, 0.5,
+ 0.5, 0.5, 1.0, 1.0, 0.5, 0.5, 1.0,
+ 1.0, 0.5, 0.5, 1.0, 1.0, 1.0, 1.0,
+ 2.0, 2.0, 1.0, 1.0, 2.0, 2.0, 1.0,
+ 1.0, 0.5, 0.5, 0.5, 0.5, 1.0, 1.0,
+ 0.5, 0.5, 1.0, 1.0, 0.5, 0.5, 1.0,
+ 1.0, 1.0, 1.0, 2.0, 2.0, 1.0, 1.0,
+ 2.0, 2.0, 1.0, 1.0, 0.5, 0.5, 0.5,
+ 0.5, 1.0, 1.0, 0.5, 0.5, 1.0, 1.0,
+ 0.5, 0.5
+ ],
+ "type": "float32"
+ }
+ },
+ {
"name": "convTranspose2d float32 4D input and filter tensors options.strides",
"inputs": {
"input": {
diff --git a/tests/wpt/tests/webnn/resources/utils.js b/tests/wpt/tests/webnn/resources/utils.js
index b8a017fb44b..5008c008e68 100644
--- a/tests/wpt/tests/webnn/resources/utils.js
+++ b/tests/wpt/tests/webnn/resources/utils.js
@@ -1288,22 +1288,6 @@ const testWriteWebNNBuffer = (testName) => {
}, `${testName} / destroy`);
promise_test(async () => {
- const descriptor = {dataType: 'int32', dimensions: [2, 2]};
- let ml_buffer = createBuffer(ml_context, descriptor);
-
- // MLBuffer was unsupported for the deviceType.
- if (ml_buffer === undefined) {
- return;
- }
-
- const array_buffer = new ArrayBuffer(sizeOfDescriptor(descriptor));
- const detached_buffer = array_buffer.transfer();
- assert_true(array_buffer.detached, 'array buffer should be detached.');
-
- ml_context.writeBuffer(ml_buffer, array_buffer);
- }, `${testName} / detached`);
-
- promise_test(async () => {
const bufferDescriptor = {dataType: 'int32', dimensions: [2, 3]};
let ml_buffer = createBuffer(ml_context, bufferDescriptor);
@@ -1407,26 +1391,6 @@ const testReadWebNNBuffer = (testName) => {
}
// Initialize the buffer.
- const input_data = [0xAA, 0xAA, 0xAA, 0xAA];
- ml_context.writeBuffer(ml_buffer, Uint8Array.from(input_data));
-
- // Writing zero bytes at the end of the buffer.
- ml_context.writeBuffer(
- ml_buffer, Uint32Array.from([0xBBBBBBBB]), /*srcOffset=*/ 1);
- await assert_buffer_data_equals(
- ml_context, ml_buffer, Uint8Array.from(input_data));
- }, `${testName} / zero_write`);
-
- promise_test(async () => {
- let ml_buffer =
- createBuffer(ml_context, {dataType: 'int32', dimensions: [1]});
-
- // MLBuffer was unsupported for the deviceType.
- if (ml_buffer === undefined) {
- return;
- }
-
- // Initialize the buffer.
ml_context.writeBuffer(
ml_buffer, Uint8Array.from([0xAA, 0xAA, 0xAA, 0xAA]));
diff --git a/tests/wpt/tests/webnn/validation_tests/convTranspose2d.https.any.js b/tests/wpt/tests/webnn/validation_tests/convTranspose2d.https.any.js
index c4717a7c538..f7cf4b3200c 100644
--- a/tests/wpt/tests/webnn/validation_tests/convTranspose2d.https.any.js
+++ b/tests/wpt/tests/webnn/validation_tests/convTranspose2d.https.any.js
@@ -446,6 +446,26 @@ const tests = [
},
},
{
+ name:
+ '[convTranspose2d] Throw if outputSizes[0] is not greater than 0.',
+ input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+ options: {
+ strides: [3, 2],
+ outputSizes: [0, 7],
+ },
+ },
+ {
+ name:
+ '[convTranspose2d] Throw if outputSizes[1] is not greater than 0.',
+ input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+ options: {
+ strides: [3, 2],
+ outputSizes: [9, 0],
+ },
+ },
+ {
name: '[convTranspose2d] Throw if the padding height is too large.',
input: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
diff --git a/tests/wpt/tests/webnn/validation_tests/gru.https.any.js b/tests/wpt/tests/webnn/validation_tests/gru.https.any.js
index b1b576e96d0..2b85ce23144 100644
--- a/tests/wpt/tests/webnn/validation_tests/gru.https.any.js
+++ b/tests/wpt/tests/webnn/validation_tests/gru.https.any.js
@@ -298,7 +298,7 @@ tests.forEach(
});
}
if (test.options.recurrentBias) {
- options.bias = builder.input('recurrentBias', {
+ options.recurrentBias = builder.input('recurrentBias', {
dataType: test.options.recurrentBias.dataType,
dimensions: test.options.recurrentBias.dimensions
});
diff --git a/tests/wpt/tests/webnn/validation_tests/gruCell.https.any.js b/tests/wpt/tests/webnn/validation_tests/gruCell.https.any.js
index 0465928d514..e37c9ec5209 100644
--- a/tests/wpt/tests/webnn/validation_tests/gruCell.https.any.js
+++ b/tests/wpt/tests/webnn/validation_tests/gruCell.https.any.js
@@ -324,7 +324,7 @@ tests.forEach(
});
}
if (test.options.recurrentBias) {
- options.bias = builder.input('recurrentBias', {
+ options.recurrentBias = builder.input('recurrentBias', {
dataType: test.options.recurrentBias.dataType,
dimensions: test.options.recurrentBias.dimensions
});
diff --git a/tests/wpt/tests/webnn/validation_tests/lstm.https.any.js b/tests/wpt/tests/webnn/validation_tests/lstm.https.any.js
index 2a3eaa1c202..06834b39604 100644
--- a/tests/wpt/tests/webnn/validation_tests/lstm.https.any.js
+++ b/tests/wpt/tests/webnn/validation_tests/lstm.https.any.js
@@ -284,7 +284,7 @@ tests.forEach(
});
}
if (test.options.recurrentBias) {
- options.bias = builder.input('recurrentBias', {
+ options.recurrentBias = builder.input('recurrentBias', {
dataType: test.options.recurrentBias.dataType,
dimensions: test.options.recurrentBias.dimensions
});
diff --git a/tests/wpt/tests/webnn/validation_tests/lstmCell.https.any.js b/tests/wpt/tests/webnn/validation_tests/lstmCell.https.any.js
index d4031b07b24..6b7bd1958b3 100644
--- a/tests/wpt/tests/webnn/validation_tests/lstmCell.https.any.js
+++ b/tests/wpt/tests/webnn/validation_tests/lstmCell.https.any.js
@@ -560,7 +560,7 @@ tests.forEach(
});
}
if (test.options.recurrentBias) {
- options.bias = builder.input('recurrentBias', {
+ options.recurrentBias = builder.input('recurrentBias', {
dataType: test.options.recurrentBias.dataType,
dimensions: test.options.recurrentBias.dimensions
});
diff --git a/tests/wpt/tests/webnn/validation_tests/pooling.https.any.js b/tests/wpt/tests/webnn/validation_tests/pooling.https.any.js
index 08a78f25baf..6d21f3d52f1 100644
--- a/tests/wpt/tests/webnn/validation_tests/pooling.https.any.js
+++ b/tests/wpt/tests/webnn/validation_tests/pooling.https.any.js
@@ -162,6 +162,26 @@ const tests = [
},
},
{
+ name: 'Throw if outputSizes[0] is not greater than 0.',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ options: {
+ windowDimensions: [2, 2],
+ padding: [2, 2, 2, 2],
+ strides: [2, 2],
+ outputSizes: [0, 4],
+ },
+ },
+ {
+ name: 'Throw if outputSizes[1] is not greater than 0.',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ options: {
+ windowDimensions: [2, 2],
+ padding: [2, 2, 2, 2],
+ strides: [2, 2],
+ outputSizes: [4, 0],
+ },
+ },
+ {
name: 'Throw if the length of window dimensions is not 2.',
input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
options: {
diff --git a/tests/wpt/tests/webnn/validation_tests/resample2d.https.any.js b/tests/wpt/tests/webnn/validation_tests/resample2d.https.any.js
index cc52cf97f4f..0c6a475e878 100644
--- a/tests/wpt/tests/webnn/validation_tests/resample2d.https.any.js
+++ b/tests/wpt/tests/webnn/validation_tests/resample2d.https.any.js
@@ -88,6 +88,16 @@ const tests = [
options: {sizes: [1, 1, 4, 6]},
},
{
+ name: '[resample2d] Throw if sizes[0] is not a valid dimension',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 4]},
+ options: {sizes: [0, 1]},
+ },
+ {
+ name: '[resample2d] Throw if sizes[1] is not a valid dimension',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 4]},
+ options: {sizes: [1, 0]},
+ },
+ {
name: '[resample2d] Throw if input data type is not floating type',
input: {dataType: 'int32', dimensions: [1, 1, 2, 4]},
options: {sizes: [1, 1, 4, 6]},
diff --git a/tests/wpt/tests/webnn/validation_tests/split.https.any.js b/tests/wpt/tests/webnn/validation_tests/split.https.any.js
index 6f7809744a7..91d00b0a6d7 100644
--- a/tests/wpt/tests/webnn/validation_tests/split.https.any.js
+++ b/tests/wpt/tests/webnn/validation_tests/split.https.any.js
@@ -52,10 +52,18 @@ const tests = [
input: {dataType: 'float32', dimensions: [2, 6]},
splits: [0],
options: {
- axis: 2,
+ axis: 0,
}
},
{
+ name: '[split] Throw if splits (scalar) is equal to 0.',
+ input: {dataType: 'float32', dimensions: [2, 6]},
+ splits: 0,
+ options: {
+ axis: 0,
+ },
+ },
+ {
name:
'[split] Throw if the splits can not evenly divide the dimension size of input along options.axis.',
input: {dataType: 'float32', dimensions: [2, 5]},
@@ -66,6 +74,15 @@ const tests = [
},
{
name:
+ '[split] Throw if splits (scalar) can not evenly divide the dimension size of input along options.axis.',
+ input: {dataType: 'float32', dimensions: [2, 5]},
+ splits: 2,
+ options: {
+ axis: 1,
+ },
+ },
+ {
+ name:
'[split] Throw if the sum of splits sizes not equal to the dimension size of input along options.axis.',
input: {dataType: 'float32', dimensions: [2, 6]},
splits: [2, 2, 3],
diff --git a/tests/wpt/tests/webnn/validation_tests/where.https.any.js b/tests/wpt/tests/webnn/validation_tests/where.https.any.js
index a26fa249315..33394f86322 100644
--- a/tests/wpt/tests/webnn/validation_tests/where.https.any.js
+++ b/tests/wpt/tests/webnn/validation_tests/where.https.any.js
@@ -15,86 +15,103 @@ const kExampleInputDescriptor = {
const tests = [
{
- name:
- '[where] Throw if the condition data type is not uint8.',
+ name: '[where] Throw if the condition data type is not uint8.',
condition: {dataType: 'float32', dimensions: [2, 4]},
- input: {dataType: 'float32', dimensions: [2, 4]},
- other: {dataType: 'float32', dimensions: [2, 4]},
+ trueValue: {dataType: 'float32', dimensions: [2, 4]},
+ falseValue: {dataType: 'float32', dimensions: [2, 4]},
},
{
name:
- '[where] Throw if the data types of input and other do not match',
+ '[where] Throw if the data types of trueValue and falseValue do not match',
condition: {dataType: 'uint8', dimensions: [2, 4]},
- input: {dataType: 'float16', dimensions: [2, 4]},
- other: {dataType: 'float32', dimensions: [2, 4]},
+ trueValue: {dataType: 'float16', dimensions: [2, 4]},
+ falseValue: {dataType: 'float32', dimensions: [2, 4]},
},
{
name:
- '[where] Throw if the shapes of input and other are not broadcastable',
+ '[where] Throw if the shapes of trueValue and falseValue are not broadcastable',
condition: {dataType: 'uint8', dimensions: [2, 4]},
- input: {dataType: 'float32', dimensions: [2, 4]},
- other: {dataType: 'float32', dimensions: [2, 3]},
+ trueValue: {dataType: 'float32', dimensions: [2, 4]},
+ falseValue: {dataType: 'float32', dimensions: [2, 3]},
},
{
- name:
- '[where] Throw if the condition shape is not broadcastable',
+ name: '[where] Throw if the condition shape is not broadcastable',
condition: {dataType: 'uint8', dimensions: [2, 4]},
- input: {dataType: 'float32', dimensions: [2, 3]},
- other: {dataType: 'float32', dimensions: [2, 1]},
+ trueValue: {dataType: 'float32', dimensions: [2, 3]},
+ falseValue: {dataType: 'float32', dimensions: [2, 1]},
},
{
name:
- '[where] Test building where with 2-D condition, 2-D input and 2-D other using broadcast',
+ '[where] Test building where with 2-D condition, 2-D trueValue and 2-D falseValue using broadcast',
condition: {dataType: 'uint8', dimensions: [2, 1]},
- input: {dataType: 'float32', dimensions: [2, 4]},
- other: {dataType: 'float32', dimensions: [2, 4]},
+ trueValue: {dataType: 'float32', dimensions: [2, 4]},
+ falseValue: {dataType: 'float32', dimensions: [2, 4]},
output: {dataType: 'float32', dimensions: [2, 4]},
},
{
name:
- '[where] Test building where with 2-D condition, 2-D input and 3-D other using broadcast',
+ '[where] Test building where with 2-D condition, 2-D trueValue and 3-D falseValue using broadcast',
condition: {dataType: 'uint8', dimensions: [1, 4]},
- input: {dataType: 'float32', dimensions: [3, 4]},
- other: {dataType: 'float32', dimensions: [2, 3, 4]},
- output: {dataType: 'float32', dimensions: [2, 3, 4]},
+ trueValue: {dataType: 'float16', dimensions: [3, 4]},
+ falseValue: {dataType: 'float16', dimensions: [2, 3, 4]},
+ output: {dataType: 'float16', dimensions: [2, 3, 4]},
},
{
name:
- '[where] Test building where with 3-D condition, 3-D input and 2-D other using broadcast',
+ '[where] Test building where with 3-D condition, 3-D trueValue and 2-D falseValue using broadcast',
condition: {dataType: 'uint8', dimensions: [2, 1, 4]},
- input: {dataType: 'float32', dimensions: [2, 3, 4]},
- other: {dataType: 'float32', dimensions: [1, 4]},
- output: {dataType: 'float32', dimensions: [2, 3, 4]},
+ trueValue: {dataType: 'int32', dimensions: [2, 3, 4]},
+ falseValue: {dataType: 'int32', dimensions: [1, 4]},
+ output: {dataType: 'int32', dimensions: [2, 3, 4]},
},
{
name:
- '[where] Test building where with 4-D condition, 3-D input and 2-D other using broadcast',
+ '[where] Test building where with 4-D condition, 3-D trueValue and 2-D falseValue using broadcast',
condition: {dataType: 'uint8', dimensions: [2, 3, 4, 5]},
- input: {dataType: 'float32', dimensions: [3, 4, 5]},
- other: {dataType: 'float32', dimensions: [4, 5]},
- output: {dataType: 'float32', dimensions: [2, 3, 4, 5]},
+ trueValue: {dataType: 'uint32', dimensions: [3, 4, 5]},
+ falseValue: {dataType: 'uint32', dimensions: [4, 5]},
+ output: {dataType: 'uint32', dimensions: [2, 3, 4, 5]},
}
];
tests.forEach(
test => promise_test(async t => {
+ for (let operand of [test.condition, test.trueValue, test.falseValue]) {
+ if (!context.opSupportLimits().input.dataTypes.includes(
+ operand.dataType)) {
+ assert_throws_js(TypeError, () => builder.input('input', {
+ dataType: operand.dataType,
+ dimensions: operand.dimensions
+ }));
+ return;
+ }
+ }
+
const condition = builder.input('condition', {
dataType: test.condition.dataType,
dimensions: test.condition.dimensions
});
- const input = builder.input(
- 'input',
- {dataType: test.input.dataType, dimensions: test.input.dimensions});
- const other = builder.input(
- 'other',
- {dataType: test.other.dataType, dimensions: test.other.dimensions});
- if (test.output) {
- const output = builder.where(condition, input, other);
+ const trueValue = builder.input('trueValue', {
+ dataType: test.trueValue.dataType,
+ dimensions: test.trueValue.dimensions
+ });
+ const falseValue = builder.input('falseValue', {
+ dataType: test.falseValue.dataType,
+ dimensions: test.falseValue.dimensions
+ });
+ if (test.output &&
+ context.opSupportLimits().where.condition.dataTypes.includes(
+ test.condition.dataType) &&
+ context.opSupportLimits().where.trueValue.dataTypes.includes(
+ test.trueValue.dataType) &&
+ context.opSupportLimits().where.falseValue.dataTypes.includes(
+ test.falseValue.dataType)) {
+ const output = builder.where(condition, trueValue, falseValue);
assert_equals(output.dataType(), test.output.dataType);
assert_array_equals(output.shape(), test.output.dimensions);
} else {
assert_throws_js(
- TypeError, () => builder.where(condition, input, other));
+ TypeError, () => builder.where(condition, trueValue, falseValue));
}
}, test.name));
@@ -102,28 +119,31 @@ multi_builder_test(async (t, builder, otherBuilder) => {
const conditionFromOtherBuilder =
otherBuilder.input('condition', kExampleConditionDescriptor);
- const input = builder.input('input', kExampleInputDescriptor);
- const other = builder.input('other', kExampleInputDescriptor);
+ const trueValue = builder.input('trueValue', kExampleInputDescriptor);
+ const falseValue = builder.input('falseValue', kExampleInputDescriptor);
assert_throws_js(
- TypeError, () => builder.where(conditionFromOtherBuilder, input, other));
+ TypeError,
+ () => builder.where(conditionFromOtherBuilder, trueValue, falseValue));
}, '[where] throw if condition is from another builder');
multi_builder_test(async (t, builder, otherBuilder) => {
- const inputFromOtherBuilder =
- otherBuilder.input('input', kExampleInputDescriptor);
+ const trueValueFromOtherBuilder =
+ otherBuilder.input('trueValue', kExampleInputDescriptor);
const condition = builder.input('condition', kExampleConditionDescriptor);
- const other = builder.input('other', kExampleInputDescriptor);
+ const falseValue = builder.input('falseValue', kExampleInputDescriptor);
assert_throws_js(
- TypeError, () => builder.where(condition, inputFromOtherBuilder, other));
-}, '[where] throw if input is from another builder');
+ TypeError,
+ () => builder.where(condition, trueValueFromOtherBuilder, falseValue));
+}, '[where] throw if trueValue is from another builder');
multi_builder_test(async (t, builder, otherBuilder) => {
- const otherFromOtherBuilder =
- otherBuilder.input('other', kExampleInputDescriptor);
+ const falseValueFromOtherBuilder =
+ otherBuilder.input('falseValue', kExampleInputDescriptor);
const condition = builder.input('condition', kExampleConditionDescriptor);
- const input = builder.input('input', kExampleInputDescriptor);
+ const trueValue = builder.input('trueValue', kExampleInputDescriptor);
assert_throws_js(
- TypeError, () => builder.where(condition, input, otherFromOtherBuilder));
-}, '[where] throw if other is from another builder');
+ TypeError,
+ () => builder.where(condition, trueValue, falseValueFromOtherBuilder));
+}, '[where] throw if falseValue is from another builder');