-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add tests for contenteditable=false elements inside contenteditable=true elements and for selection in empty elements #35862
Add tests for contenteditable=false elements inside contenteditable=true elements and for selection in empty elements #35862
Conversation
@masayuki-nakano could you look at these tests? https://wpt.fyi/results/selection?label=pr_head&max-count=1&pr=35862 suggests that the cases covered are where Blink and Gecko agree but WebKit is different. |
editing/empty-elements-manual.html
Outdated
<ol> | ||
<li>Put the caret inside the red square above.</li> | ||
<li>Press any letter to insert it.</li> | ||
</ol> | ||
<script> | ||
setup( { explicit_timeout: true } ); | ||
|
||
promise_test( () => { | ||
return new Promise( ( resolve, reject ) => { | ||
host.addEventListener( 'input', () => { | ||
try { | ||
assert_greater_than( strong.innerHTML.length, 0, 'The text was inserted into the <strong> element' ); | ||
} catch ( error ) { | ||
reject( error ); | ||
} | ||
resolve(); | ||
} ); | ||
} ); | ||
} ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why cannot write this as:
const actions = new test_driver.Actions();
await actions
.pointerMove(0, 0, {origin: document.querySelector("strong")})
.pointerDown({button: actions.ButtonType.LEFT})
.pointerUp({button: actions.ButtonType.LEFT})
.send();
document.execCommand("insertText", false, "a");
? It seems that Chrome and Firefox work as same as I do same thing in them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And should this be work with completely empty inline element (i.e., without margin
, padding
, border
, etc)? Then, it should be tested too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the advice to use the test_driver
! I didn't discover it while writing tests and that's why I decided to make them manual. I've rewritten them using test_driver
.
And should this be work with completely empty inline element (i.e., without
margin
,padding
,border
, etc)? Then, it should be tested too.
I've added one analogous to the test with a styled element. However, I'm not sure if it's the right one – as it emulates the user clicking in the element, yet the element has zero height.
const cefalse = document.querySelector( '#cefalse' ); | ||
const paragraph = document.querySelector( '#paragraph' ); | ||
return new Promise( ( resolve, reject ) => { | ||
host.addEventListener( 'keyup', ( { key }) => { | ||
if ( key !== 'Backspace' && key !== 'Delete' ) { | ||
return; | ||
} | ||
|
||
try { | ||
assert_false( cefalse.isConnected, 'cE=false element was removed' ); | ||
assert_false( paragraph.isConnected, 'paragraph was removed' ); | ||
} catch ( error ) { | ||
reject( error ); | ||
} | ||
resolve(); | ||
} ); | ||
} ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that the following code gets same result as I test this manually:
const cefalse = document.querySelector( '#cefalse' );
const paragraph = document.querySelector( '#paragraph' );
getSelection().collapse(paragraph, 0);
document.execCommand("selectAll");
document.execCommand("delete");
assert_false( cefalse.isConnected, 'cE=false element was removed' );
assert_false( paragraph.isConnected, 'paragraph was removed' );
And the other manual tests can be rewritten with this style.
Currently, Gecko allows to delete non-editable nodes which are children of editable nodes. And creating new text node when selection is collapsed in an empty element is reasonable to me. However, I'm not sure about the cases when selection range boundary is in an empty inline element, e.g.,
In this case, Similarly,
In this case, I think that in the Chrome's rules, the first expectations are reasonable because Chrome keeps the style of start boundary of the range as far as I've investigated. |
I tend to agree with Chrome on that – keeping the style of start boundary is something we do in CKEditor. I've also added an additional test for inserting text on the boundaries of a link – browsers jump out of the link, even if the range is still inside it. It's inconsistent with other elements, e.g. @masayuki-nakano, thanks for the review! I've updated the PR according to your advice. Thanks to that I was able to move all tests into two files: |
I apologize the long delay.
Well, but it's something a little bit complicated if first visible content is not text, e.g.,
I don't think it's reasonable because it makes it impossible that web apps cannot make users keep tying in a link with managing FYI: Gecko does something special in this case, but for compatibility with the other browsers and traditional behavior which is important for Thunderbird. Gecko respects
Yeah, I don't like the inconsistent behavior because browsers need to handle it specially in some places in builtin editor implementation. However, I think that the inconsistency comes from traditional behaviors of some word processors especially "Word". Therefore, I think that the special handling may be expected by the users.
Really sorry for the long delay. I'll check it soon. |
document.execCommand( 'selectAll' ); | ||
document.execCommand( 'delete' ); | ||
|
||
assert_false( cefalse.isConnected, 'cE=false element was removed' ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Although I don't know whether there are some discussion about WPT test messages. For making the message clearer, I recommend to use "should" or "shouldn't" in the messages. For example, "cE=false element should be removed".
If this test detects a regression, it'll see "cE=false element was removed", but it's not actually. Therefore, this message anyway makes browser developers confused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds reasonable, I've updated the messages.
<div contenteditable> | ||
<div contenteditable="false" id="cefalse-beginning"> </div> | ||
<p id="paragraph-beginning">Lorem ipsum dolor sit amet.</p> | ||
</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this test, you define the DOM tree first, then, check it below where it's far from here.
If I were you, I'd put <div contenteditable>
only and initialize its content with setting .innerHTML
. Then, only reading test()
or promise_test()
allows developers to understand what it checks easier. Like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I didn't notice these utils. Tests rewritten with them are more readable now.
test( () => { | ||
const cefalse = document.querySelector( '#cefalse-beginning' ); | ||
const paragraph = document.querySelector( '#paragraph-beginning' ); | ||
getSelection().collapse( paragraph, 0 ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that just calling focus
of div[contenteditable]
is faster and enough for the tests.
.send(); | ||
document.execCommand( 'insertText', false, 'a' ); | ||
assert_greater_than( target.innerHTML.length, 0, 'The text was inserted into the styled <strong> element' ); | ||
}, 'Insert text into the inline element styled with border and padding' ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that you should separate test for <strong>
element which only has border
or padding
.
On the other hand, you probably intended to check whether it should or should not work with visible vs. implicitly invisible elements. If so, there could be more tricky cases like ::before
etc?
And I wonder, this is not directly related to editing. If this test requests some browsers to change the behavior, it seems that they need to change the Selection
result when it's clicked. Perhaps, you should add such test to the directory of Selection API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've separated tests for border
and padding
.
I didn't move these tests to the selection API dir, though. As can be seen in a simple demo, the selection seems to be correct (at least in the case of the visible span
). In Safari and Chrome this case works, Firefox jumps out of the element when anything is typed inside.
If so, there could be more tricky cases like
::before
etc?
Probably but I don't think such use cases are really sensible in the editing context. The ::before
pseudoelement is not selectable and I can't think of any real use case for changing that behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't move these tests to the selection API dir, though. As can be seen in a simple demo, the selection seems to be correct (at least in the case of the visible
span
). In Safari and Chrome this case works, Firefox jumps out of the element when anything is typed inside.
Thank you for explaining the symptom. Indeed, it should work in Firefox too. I'll take a look after current big work ends.
If so, there could be more tricky cases like
::before
etc?Probably but I don't think such use cases are really sensible in the editing context. The
::before
pseudoelement is not selectable and I can't think of any real use case for changing that behavior.
If Chrome nor Safari does not treat the elements having only CSS generated content, it makes the fix harder in Firefox because Firefox needs to check whether ::before
and/or ::after
is the only content in it. Therefore, anyway the behavior is important for me to making it fix in Firefox. Therefore, could you add the cases too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delayed response.
I think that I initially misunderstood the case with ::before
and ::after
pseudoelements. For some reason, I thought about putting selection in the pseudoelements. But I suppose you meant being able to put the selection in the empty elements with only the pseudoelements – is my understanding correct? If the latter case is the one you thought of, then I agree that it would make sense to support inserting text also in such empty inline elements.
I added some tests for the second case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delayed response.
Me too, no worry.
But I suppose you meant being able to put the selection in the empty elements with only the pseudoelements – is my understanding correct?
Yes, that's right. My intention was adding the case which may create a "frame" for empty element only with CSS styling.
I added some tests for the second case.
Thank you!
selection.collapseToEnd(); | ||
|
||
document.execCommand( 'insertText', false, 'a' ); | ||
assert_equals( target.innerHTML, 'Linka', 'The text was inserted into the link' ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, which do browsers pass or fail these tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Firefox passes both tests, Safari and Chrome fail both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Especially Chrome "normalizes" Selection
before handling edit operations/commands. So the result is what I can imagine. (When I make Firefox emulate the behavior, odd behavior caused by the normalization make the code complicated...)
FYI: I'm not a formal reviewer of WPT. Therefore, I'm not sure about the details of the rules around the test message, formatting the code (especially around the white-spaces around And perhaps, these tests should be in |
There are no reviewers for this pull request. Please reach out on the chat room to get help with this. Thank you! |
Yes, that sounds like a plausible explanation for the current link behavior. @masayuki-nakano, thanks for the review. I've further updated the tests, following your advice. |
There aren't strongly enforced rules for js formatting because we don't have a toolchain to enforce them, and we have contributions coming from multiple projects, and don't want things to be blocked on formatting choices. So generally it's up to your discretion, but of course code that looks like the surrounding code is better than code that's introduces new conventions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you very much!
…rue elements and for selection in empty elements (web-platform-tests#35862) * Add tests for selecting and deleting cE=false elements on the boundaries of cE=true elements. * Add tests for empty inline element.
I've been asked by @johanneswilm to prepare some tests for selection issues in CKEditor as a part of Interop 2022.
There are two main issues for us:
[contenteditable=false]
elements inside the[contenteditable=true]
elements (especially at their beginning and end),Tests
For the first issue I've prepared 4 tests:
selection/contenteditable/cefalse-on-boundaries.html
,editing/cefalse-beginning-deletion-manual.html
,editing/cefalse-boundaries-deletion-manual.html
,editing/cefalse-end-deletion-manual.html
.Unfortunately, manual tests were needed because Chrome shows the correct selection and it seems to work fine with the JS API (e.g.
range#deleteContents()
) but trying to interact in person with such selection results in a broken experience (the selected content can't be deleted).For the issue with putting selection inside empty elements, I've prepared two tests:
selection/caret/empty-elements.html
,editing/empty-elements-manual.html
.Once again, a manual test was needed as Firefox allows putting the selection inside such an element but typing inside it does not work (the typed content is moved outside the empty element).
Context
The first issue required us to create dedicated plugins for CKEditor 4 and CKEditor 5 whose task is to add editable elements around non-editable ones (especially ones at the beginning or end of the editor) to fix the incorrect selection of them. However, in CKE4, even with this plugin, the issue is still present, especially in Safari.
The second one requires us to use various fillers (CKEditor 4 version and CKEditor 5 one) to be able to put the selection in such places. There is also a similar issue connected with editing at the edge of elements, e.g. links. As visible in CKEditor's 5 link demo, putting the caret inside any of the links, moving it to the end of the link, and pressing space will produce an
filler, needed to inform the browser that the intention is to stay inside the link.