diff options
author | Servo WPT Sync <32481905+servo-wpt-sync@users.noreply.github.com> | 2024-07-14 05:16:30 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-14 09:16:30 +0000 |
commit | 968474a9fda45b951de9b9b16b5a1fae16c5f2a7 (patch) | |
tree | c47b6bff4605d271dd47923c62f11d747ea0c6b9 | |
parent | 3118542a9e90478cdabf1f2479851f86dd0e94d6 (diff) | |
download | servo-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>
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;">𝐴</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;">A</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><-- 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><-- 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'); |