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
Fixed issue with textwatcher throttle invalid selection #2376
Conversation
… needs to refresh it afterwards anyways due to #2373.
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 fixes the error, yes, but it takes away optimization that we want our devs to have - that we make sure that callback is not called by check too often.
See the comments for more info.
on: { | ||
instanceReady: function() { | ||
new CKEDITOR.plugins.textWatcher( editor, function( selectionRange ) { | ||
var isSelCorrect = editor.getSelection().getRanges()[0].startContainer.equals( selectionRange.startContainer ); |
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.
Code style, missing spaces in [0]
.
|
||
bot.setHtmlWithSelection( '<b>xxx</b><i>[yyy]</i>' ); | ||
|
||
setTimeout( function() { |
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 don't know why this test is asynchronous and has a timeout? The assertion could be placed straight inside callback passed to attachTextWatcher
making test synchronous and allowing us to drop this timeout.
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.
Not sure if I understand you correctly. The issue occurs when the selection changes between throttled inputs and the late input is fired with an invalid selection.
Also, the issue is only reproducible with enabled throttling.
How could we test this case with the synchronous path? There will be no time to broke selection between textwatcher.check
and textwatcher._buffer.input
execution.
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.
What this test is doing is essentially an equivalent of the following:
// (#2373)
'test throttle synchronization': function() {
var editor = this.editor,
bot = this.editorBot,
textWatcher = attachTextWatcher( editor, function( selectionRange ) {
var range = editor.getSelection().getRanges()[ 0 ];
if ( editor.getSelection().getSelectedText() === 'yyy' ) {
resume( function() {
assert.isTrue( range.startContainer.equals( selectionRange.startContainer ) );
} );
}
return {
text: 'test'
};
}, 100 );
bot.setHtmlWithSelection( '<b>[xxx]</b><i>yyy</i>' );
textWatcher.check( {} );
textWatcher.check( {} );
bot.setHtmlWithSelection( '<b>xxx</b><i>[yyy]</i>' );
wait();
},
Note that there is no timeout. Same can be used for the next test case.
plugins/textwatcher/plugin.js
Outdated
this._listeners.push( editable.attachListener( editable, 'keyup', check, this ) ); | ||
} | ||
|
||
// CKEditor's event system has a limitation that one function (in this case this.check) |
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.
That's nice that we were able to drop this quirk code 👍
plugins/textwatcher/plugin.js
Outdated
@@ -253,7 +232,18 @@ | |||
return; | |||
} | |||
|
|||
this._buffer.input( selectionRange ); | |||
var matched = this.callback( selectionRange ); |
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.
By inlining this code we're breaking the feature of this API, that check
's are throttled out of the box, and end user does not need to worry about it.
With this proposition you're changing how the API behaves, and it will always call the TextWatcher.callback()
which might be expensive.
What if we change thinking a bit? What if the actual fetch of selection and range happens inside the throttled function? It would make sense to put it in both places, I mean: leave it where it is now on master
and put inside throttled function.
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 pushed solution based on above in the t/2373b branch. It also results with smaller LoC added compared to the original code.
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.
Note that there's one issue - in case if during thorttled call there's no selection (edge case, but we need to consider it) there's an early return.
https://github.com/ckeditor/ckeditor-dev/blob/b5e9817/plugins/textwatcher/plugin.js#L177-L179
It would be much better to notify unmatch, because we're no longer matching anything, right? It would be nice to add unit test coverage for this edge case.
6a840ac
to
c36da9d
Compare
You are right that I accidentally removed the throttling feature from |
…at no unmatch event is fired if text was not matched.
…text was matched before.
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.
Please simplify the tests a little bit, ignore the test for mobile and I think we're good to go.
plugins/textwatcher/plugin.js
Outdated
// There are cases where the selection needs to be refreshed after debounce. (#2373) | ||
var selectionRange = this._getRange( this.editor ); | ||
|
||
if ( !selectionRange ) { |
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.
This code can be further simplified. Now you have two separate code branches that call this.unmatch()
if that's possible you want to reuse code branches if possible - that increases the readability.
Also this code has a bug as it is possible that it will fire unmatch
event even if nothing was matched prior testTextMatch
call. This is exactly what is happening in your test case. See test empty selection does not fire stray unmatched event
unit test, and revert plugins\textwatcher\plugin.js
file to previous version to see how it works.
I have fixed this issue in c8bca2a.
<div id="editor"></div> | ||
|
||
<script> | ||
var statusEl = document.getElementById( 'status' ), |
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.
This manual test case is not relevant for mobile browsers, and should be ignored.
I have added a couple of commits that do address some issues. |
What is the purpose of this pull request?
Bug fix
Does your PR contain necessary tests?
All patches which change the editor code must include tests. You can always read more
on PR testing,
how to set the testing environment and
how to create tests
in the official CKEditor documentation.
This PR contains
What changes did you make?
Throttling textwatcher#check function sometimes results with out of sync selection. I moved selection getter into a throttled function.
Closes #2373