diff --git a/CHANGES.md b/CHANGES.md index c0234477ee7..992f9f2e026 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -32,6 +32,7 @@ Fixed Issues: * [#12008](http://dev.ckeditor.com/ticket/12008): Fixed various cases of inserting a single non-editable element using the [`editor.insertHtml()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-insertHtml) method. Fixes pasting a widget with a nested editable inside another widget's nested editable. * [#12148](http://dev.ckeditor.com/ticket/12148): Fixed: [`dom.element.getChild()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.element-method-getChild) should not modify passed array. * [#12874](http://dev.ckeditor.com/ticket/12874): Fixed: Information about aggregated tasks should be somehow accessible in [aggregated#finished](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.notificationAggregator-event-finished). +* [#12503](http://dev.ckeditor.com/ticket/12503): [Blink/Webkit] Fixed: Incorrect result of select all, backspace/delete. Other Changes: diff --git a/core/editable.js b/core/editable.js index 08d44a11dab..2c564f47b80 100644 --- a/core/editable.js +++ b/core/editable.js @@ -2400,6 +2400,9 @@ if ( ( bogus = startBlock.getBogus() ) ) bogus.remove(); + // Changing end container to element from text node (#12503). + range.enlarge( CKEDITOR.ENLARGE_INLINE ); + // Delete range contents. Do NOT merge. Merging is weird. range.deleteContents(); @@ -2419,6 +2422,13 @@ // Make sure the result selection is collapsed. range = editor.getSelection().getRanges()[ 0 ]; range.collapse( 1 ); + + // Optimizing range containers from text nodes to elements (#12503). + range.optimize(); + if ( range.startContainer.getHtml() === '' ) { + range.startContainer.appendBogus(); + } + range.select(); return true; diff --git a/tests/core/editable/keystrokes/delbackspacequirks/_helpers/tools.js b/tests/core/editable/keystrokes/delbackspacequirks/_helpers/tools.js index c37cbbbc798..f2d8d55adff 100644 --- a/tests/core/editable/keystrokes/delbackspacequirks/_helpers/tools.js +++ b/tests/core/editable/keystrokes/delbackspacequirks/_helpers/tools.js @@ -11,9 +11,11 @@ var quirksTools = ( function() { 8: 'BACKSPACE' }; - function assertKeystroke( key, keyModifiers, handled, html, expected ) { + function assertKeystroke( key, keyModifiers, handled, html, expected, normalizeSelection ) { + normalizeSelection = ( normalizeSelection === false ) ? false : true; + function decodeBoguses( html ) { - return html.replace( /@/g, '
' ); + return html.replace( /@/g, CKEDITOR.env.needsBrFiller ? '
' : '' ); } return function() { @@ -23,7 +25,7 @@ var quirksTools = ( function() { html = decodeBoguses( html ); - bot.htmlWithSelection( html ); + bender.tools.selection.setWithHtml( editor, html ); var listener = editor.on( 'key', function() { ++handledNatively; @@ -35,8 +37,16 @@ var quirksTools = ( function() { shiftKey: keyModifiers & CKEDITOR.SHIFT } ) ); - assert.areSame( decodeBoguses( expected ), - bender.tools.getHtmlWithSelection( editor.editable(), editor.document ).replace( /\u200b/g, '' ), '(' + keyNames[ key ] + ') Correct DOM state after the keystroke' ); + var htmlWithSelection = bender.tools.selection.getWithHtml( editor ); + var message = '(' + keyNames[ key ] + ') Correct DOM state after the keystroke'; + + assert.isInnerHtmlMatching( + expected, + htmlWithSelection, + { compareSelection: true, normalizeSelection: normalizeSelection }, + message + ); + assert.areSame( handled, handledNatively, '(' + keyNames[ key ] + ') Keystroke handled by the browser' ); listener.removeListener(); @@ -52,11 +62,11 @@ var quirksTools = ( function() { } function bf( html ) { - return assertKeystroke.apply( this, [ BACKSPACE, 0, 1, html, html ] ); + return assertKeystroke.apply( this, [ BACKSPACE, 0, 1, html, html, false ] ); } function df( html ) { - return assertKeystroke.apply( this, [ DEL, 0, 1, html, html ] ); + return assertKeystroke.apply( this, [ DEL, 0, 1, html, html, false ] ); } return { @@ -92,4 +102,4 @@ var quirksTools = ( function() { }; } }; -} )(); \ No newline at end of file +} )(); diff --git a/tests/core/editable/keystrokes/delbackspacequirks/collapsed.js b/tests/core/editable/keystrokes/delbackspacequirks/collapsed.js index dbd5a7daedf..e3b11bfc5a0 100644 --- a/tests/core/editable/keystrokes/delbackspacequirks/collapsed.js +++ b/tests/core/editable/keystrokes/delbackspacequirks/collapsed.js @@ -36,7 +36,7 @@ this.editorBot.setData( '', function() { editor.resetUndo(); - b( '

x

^y

', '

x^y

' ).call( tc ); + b( '

x

{}y

', '

x^y

' ).call( tc ); editor.execCommand( 'undo' ); assert.areSame( '

x

y

', editor.getData(), 'after 1st undo' ); @@ -59,125 +59,125 @@ } ); }, - 'test CTRL+backspace works as backspace when merging blocks': assertKeystroke( BACKSPACE, CKEDITOR.CTRL, 0, '

x

^y

', '

x^y

' ), - 'test SHIFT+backspace works as backspace when merging blocks': assertKeystroke( BACKSPACE, CKEDITOR.SHIFT, 0, '

x

^y

', '

x^y

' ), - 'test CTRL+delete works as delete when merging blocks': assertKeystroke( DEL, CKEDITOR.CTRL, 0, '

x^

y

', '

x^y

' ), - 'test SHIFT+delete works as delete when merging blocks': assertKeystroke( DEL, CKEDITOR.SHIFT, 0, '

x^

y

', '

x^y

' ), + 'test CTRL+backspace works as backspace when merging blocks': assertKeystroke( BACKSPACE, CKEDITOR.CTRL, 0, '

x

{}y

', '

x^y

' ), + 'test SHIFT+backspace works as backspace when merging blocks': assertKeystroke( BACKSPACE, CKEDITOR.SHIFT, 0, '

x

{}y

', '

x^y

' ), + 'test CTRL+delete works as delete when merging blocks': assertKeystroke( DEL, CKEDITOR.CTRL, 0, '

x{}

y

', '

x^y

' ), + 'test SHIFT+delete works as delete when merging blocks': assertKeystroke( DEL, CKEDITOR.SHIFT, 0, '

x{}

y

', '

x^y

' ), - 'test CTRL+backspace is not handled when not merging blocks': assertKeystroke( BACKSPACE, CKEDITOR.CTRL, 1, '

x^y

', '

x^y

' ), - 'test SHIFT+backspace is not handled when not merging blocks': assertKeystroke( BACKSPACE, CKEDITOR.SHIFT, 1, '

x^y

', '

x^y

' ), - 'test CTRL+delete is not handled when not merging blocks': assertKeystroke( DEL, CKEDITOR.CTRL, 1, '

x^y

', '

x^y

' ), - 'test SHIFT+delete is not handled when not merging blocks': assertKeystroke( DEL, CKEDITOR.SHIFT, 1, '

x^y

', '

x^y

' ), + 'test CTRL+backspace is not handled when not merging blocks': assertKeystroke( BACKSPACE, CKEDITOR.CTRL, 1, '

x{}y

', '

x^y

' ), + 'test SHIFT+backspace is not handled when not merging blocks': assertKeystroke( BACKSPACE, CKEDITOR.SHIFT, 1, '

x{}y

', '

x^y

' ), + 'test CTRL+delete is not handled when not merging blocks': assertKeystroke( DEL, CKEDITOR.CTRL, 1, '

x{}y

', '

x^y

' ), + 'test SHIFT+delete is not handled when not merging blocks': assertKeystroke( DEL, CKEDITOR.SHIFT, 1, '

x{}y

', '

x^y

' ), // --- BACKSPACE ------------------------------------------------------ - 'test backspace #1': b( '

x

^y

', '

x^y

' ), - 'test backspace #2': b( '

x

^y

', '

x^y

' ), - 'test backspace #3': b( '

x

^y

', '

x^y

' ), - 'test backspace #4': b( '

x

^y

', '

x^y

' ), - 'test backspace #5': b( '

x

^y

', '

x^y

' ), - 'test backspace #6': b( '

x

^y

', '

x^y

' ), - 'test backspace #7': b( '

x

^foo

', '

x^foo

' ), - 'test backspace #8': b( '

x

^y

', '

x^y

' ), - 'test backspace #9': b( '

foo

^y

', + 'test backspace #1': b( '

x

[]y

', '

x^y

' ), + 'test backspace #2': b( '

x

[]y

', '

x^y

' ), + 'test backspace #3': b( '

x

[]y

', '

x^y

' ), + 'test backspace #4': b( '

x

[]y

', '

x^y

' ), + 'test backspace #5': b( '

x

[]y

', '

x^y

' ), + 'test backspace #6': b( '

x

[]y

', '

x^y

' ), + 'test backspace #7': b( '

x

[]foo

', '

x^foo

' ), + 'test backspace #8': b( '

x

[]y

', '

x^y

' ), + 'test backspace #9': b( '

foo

{}y

', '

foo^y

' ), - 'test backspace #10': b( '

x

^

', '

x^

' ), - 'test backspace #11': b( '', '' ), - 'test backspace #12': b( '

x

^y

', + 'test backspace #10': b( '

x

[]@

', '

x^@!

' ), + 'test backspace #11': b( '', '' ), + 'test backspace #12': b( '

x

[]y

', '

x^y

' ), - 'test backspace #13': b( '

^y

', '' ), - 'test backspace #14': b( '

^y

z

', '

z

' ), - 'test backspace #15': b( '

a


^y

', '

a

^y

' ), - 'test backspace #16': b( '

^y

', '

^y

' ), - 'test backspace #17': b( '

a



^y

', '

a


^y

' ), + 'test backspace #13': b( '

[]y

', '' ), + 'test backspace #14': b( '

[]y

z

', '

z

' ), + 'test backspace #15': b( '

a


[]y

', '

a

^y

' ), + 'test backspace #16': b( '

[]y

', '

^y

' ), + 'test backspace #17': b( '

a



[]y

', '

a


^y

' ), // Merge inline elements after keystroke. - 'test backspace, merge #1': b( '

x

^y

', '

x^y

' ), - 'test backspace, merge #2': b( '

x

^y

', '

x^y

' ), - 'test backspace, merge #3': b( '

x

^y

', '

x^y

' ), - 'test backspace, merge #4': b( '

x

^y

z

', '

x^y

z

' ), - 'test backspace, merge #5': b( '

x

^y

', '

x^y

' ), - 'test backspace, merge #6': b( '

x

^y

', '

x^y

' ), - 'test backspace, merge #7': b( '

x

^y

', '

x^y

' ), - 'test backspace, merge #8': b( '

x

^y

', + 'test backspace, merge #1': b( '

x

[]y

', '

x^y

' ), + 'test backspace, merge #2': b( '

x

[]y

', '

x^y

' ), + 'test backspace, merge #3': b( '

x

[]y

', '

x^y

' ), + 'test backspace, merge #4': b( '

x

[]y

z

', '

x^y

z

' ), + 'test backspace, merge #5': b( '

x

[]y

', '

x^y

' ), + 'test backspace, merge #6': b( '

x

[]y

', '

x^y

' ), + 'test backspace, merge #7': b( '

x

[]y

', '

x^y

' ), + 'test backspace, merge #8': b( '

x

[]y

', '

x^y

' ), - 'test backspace, merge #9': b( '

x

^y

', + 'test backspace, merge #9': b( '

x

[]y

', '

x^y

' ), // Boguses. - 'test backspace, bogus #1': b( '

@

^y

', '

^y

' ), - 'test backspace, bogus #2': b( '

@

^@

', '

^@

' ), - 'test backspace, bogus #3': b( '

x

^@

', '

x^@

' ), - 'test backspace, bogus #4': b( '


@

^@

', '


^@

' ), + 'test backspace, bogus #1': b( '

@

[]y

', '

^y

' ), + 'test backspace, bogus #2': b( '

@

[]@

', '

^@!

' ), + 'test backspace, bogus #3': b( '

x

[]@

', '

x^@!

' ), + 'test backspace, bogus #4': b( '


@

[]@

', '


^@!

' ), // False positives. Some of them are buggy, but it's a different case e.g. not merging blocks. - 'test backspace, no action #1': bf( '

x

y^y

' ), - 'test backspace, no action #2': bf( 'x

^y

' ), - 'test backspace, no action #3': bf( '

x

y^y

' ), - 'test backspace, no action #4': bf( '

x

y^y

' ), - 'test backspace, no action #5': bf( '

x

z

^y

' ), - 'test backspace, no action #6': bf( 'x

^y

' ), - 'test backspace, no action #7': bf( '

x

z

^y

' ), + 'test backspace, no action #1': bf( '

x

y{}y

' ), + 'test backspace, no action #2': bf( 'x

{}y

' ), + 'test backspace, no action #3': bf( '

x

y{}y

' ), + 'test backspace, no action #4': bf( '

x

y{}y

' ), + 'test backspace, no action #5': bf( '

x

z

{}y

' ), + 'test backspace, no action #6': bf( 'x

{}y

' ), + 'test backspace, no action #7': bf( '

x

z

{}y

' ), // Handled by list or table plugin or editable, but not related to #9998. // This is just to control whether the fix for #9998 does not break some case which it should not handle at all. - 'test backspace, excluded #1': b( '', '' ), - 'test backspace, excluded #2': b( '

x

', '

x

^y

' ), - 'test backspace, excluded #3': b( '

x

', '

x

^y

' ), - 'test backspace, excluded #4': b( '

x

', '

x

' ), - 'test backspace, excluded #5': b( '
x^y
', '
x^y
' ), - 'test backspace, excluded #6': b( '

x

^y

', + 'test backspace, excluded #1': b( '', '' ), + 'test backspace, excluded #2': b( '

x

', '

x

^y

' ), + 'test backspace, excluded #3': b( '

x

', '

x

^y

' ), + 'test backspace, excluded #4': b( '

x

', '

x

' ), + 'test backspace, excluded #5': b( '
x[]y
', '
x^y
' ), + 'test backspace, excluded #6': b( '

x

[]y

', '

x

^y

' ), - 'test backspace, excluded #7': b( '

x

^y
', '

x^

y
' ), + 'test backspace, excluded #7': b( '

x

[]y
', '

x^

y
' ), // --- DELETE --------------------------------------------------------- - 'test delete #1': d( '

x^

y

', '

x^y

' ), - 'test delete #2': d( '

x^

y

', '

x^y

' ), - 'test delete #3': d( '

x^

y

', '

x^y

' ), - 'test delete #4': d( '

x^

y

', '

x^y

' ), - 'test delete #5': d( '

x^

y

', '

x^y

' ), - 'test delete #6': d( '

x^

y

', '

x^y

' ), - 'test delete #7': d( '

foo^

x

', '

foo^x

' ), - 'test delete #8': d( '

x^

y

', '

x^y

' ), - 'test delete #9': d( '

x^

foo

', + 'test delete #1': d( '

x[]

y

', '

x^y

' ), + 'test delete #2': d( '

x[]

y

', '

x^y

' ), + 'test delete #3': d( '

x[]

y

', '

x^y

' ), + 'test delete #4': d( '

x[]

y

', '

x^y

' ), + 'test delete #5': d( '

x[]

y

', '

x^y

' ), + 'test delete #6': d( '

x[]

y

', '

x^y

' ), + 'test delete #7': d( '

foo[]

x

', '

foo^x

' ), + 'test delete #8': d( '

x[]

y

', '

x^y

' ), + 'test delete #9': d( '

x[]

foo

', '

x^foo

' ), - 'test delete #10': d( '

^

y

', '

^y

' ), - 'test delete #13': d( '

y

', '' ), - 'test delete #14': d( '

y

z

', '

z

' ), - 'test delete #15': d( '

y^


a

', '

y^

a

' ), - 'test delete #16': d( '

y^


', '

y^

' ), - 'test delete #17': d( '

y^



a

', '

y^


a

' ), + 'test delete #10': d( '

[]@

y

', '

^y

' ), + 'test delete #13': d( '

y

', '' ), + 'test delete #14': d( '

y

z

', '

z

' ), + 'test delete #15': d( '

y[]


a

', '

y^

a

' ), + 'test delete #16': d( '

y[]


', '

y^

' ), + 'test delete #17': d( '

y[]



a

', '

y^


a

' ), // Merge inline elements after keystroke. - 'test delete, merge #1': d( '

x^

y

', '

x^y

' ), - 'test delete, merge #2': d( '

x^

y

', '

x^y

' ), - 'test delete, merge #3': d( '

x^

y

', '

x^y

' ), - 'test delete, merge #4': d( '

x^

y

z

', '

x^y

z

' ), + 'test delete, merge #1': d( '

x[]

y

', '

x^y

' ), + 'test delete, merge #2': d( '

x[]

y

', '

x^y

' ), + 'test delete, merge #3': d( '

x[]

y

', '

x^y

' ), + 'test delete, merge #4': d( '

x[]

y

z

', '

x^y

z

' ), // Boguses. - 'test delete, bogus #1': d( '

x^

@

', '

x^@

' ), - 'test delete, bogus #2': d( '

@^

@

', '

^@

' ), - 'test delete, bogus #3': d( '

^@

x

', '

^x

' ), - 'test delete, bogus #4': d( '

^@


@

', '

^
@

' ), + 'test delete, bogus #1': d( '

x[]

@

', '

x^@!

' ), + 'test delete, bogus #2': d( '

@[]

@

', '

^@!

' ), + 'test delete, bogus #3': d( '

[]@

x

', '

^x

' ), + 'test delete, bogus #4': d( '

[]@


@

', '

^
@!

' ), // False positives. Some of them are buggy, but it's a different case e.g. not merging blocks. - 'test delete, no action #1': df( '

x^x

y

' ), - 'test delete, no action #2': df( '

x^

y' ), - 'test delete, no action #3': df( '

x^x

y

' ), - 'test delete, no action #4': df( '

x^x

y

' ), - 'test delete, no action #5': df( '

x^

x

y

' ), - 'test delete, no action #6': df( '

x^

y' ), - 'test delete, no action #7': df( '

y^

y

z

' ), + 'test delete, no action #1': df( '

x{}x

y

' ), + 'test delete, no action #2': df( '

x{}

y' ), + 'test delete, no action #3': df( '

x{}x

y

' ), + 'test delete, no action #4': df( '

x{}x

y

' ), + 'test delete, no action #5': df( '

x{}

x

y

' ), + 'test delete, no action #6': df( '

x{}

y' ), + 'test delete, no action #7': df( '

y{}

y

z

' ), // Handled by list or table plugin or editable, but not related to #9998. // This is just to control whether the fix for #9998 does not break some case which it should not handle at all. - 'test delete, excluded #1': d( '', '' ), - 'test delete, excluded #2': d( '

y

', '' ), - 'test delete, excluded #3': d( '

y

', '' ), - 'test delete, excluded #4': d( '
x^y
', '
x^y
' ), - 'test delete, excluded #5': d( '

x^

y

', + 'test delete, excluded #1': d( '', '' ), + 'test delete, excluded #2': d( '

y

', '' ), + 'test delete, excluded #3': d( '

y

', '' ), + 'test delete, excluded #4': d( '
x[]y
', '
x^y
' ), + 'test delete, excluded #5': d( '

x[]

y

', '

x^

y

' ), - 'test delete, excluded #6': d( '
x^

y

', '
x

^y

' ) + 'test delete, excluded #6': d( '
x[]

y

', '
x

^y

' ) } ); -} )( quirksTools ); \ No newline at end of file +} )( quirksTools ); diff --git a/tests/core/editable/keystrokes/delbackspacequirks/expanded.js b/tests/core/editable/keystrokes/delbackspacequirks/expanded.js index e784ed212a4..b82a906c147 100644 --- a/tests/core/editable/keystrokes/delbackspacequirks/expanded.js +++ b/tests/core/editable/keystrokes/delbackspacequirks/expanded.js @@ -14,6 +14,9 @@ bender.test( { setUp: function() { + // Preventing removing empty tag. + delete CKEDITOR.dtd.$removeEmpty.small; + if ( !CKEDITOR.env.webkit ) assert.ignore(); }, @@ -79,6 +82,9 @@ 'test backspace and delete, bogus #3': bd( '

[@

]@

', '

^@

' ), 'test backspace and delete, bogus #4': bd( '

@[

]@

', '

^@

' ), + // #12503. + 'test backspace and delete, bogus #5': bd( '

{Foo

bar

baz}

', '

^@!

' ), + // Merge inline elements after keystroke. 'test backspace and delete, no action #1': bdf( '
x[x

y]y

' ), 'test backspace and delete, no action #2': bdf( '
x[xzz

y]y

' ), @@ -87,4 +93,4 @@ 'test backspace and delete, no action #5': bdf( '

x[xy]y

' ), 'test backspace and delete, no action #6': bdf( '
x[x
y]y
' ) } ); -} )( quirksTools.bd, quirksTools.bdf, quirksTools.b ); \ No newline at end of file +} )( quirksTools.bd, quirksTools.bdf, quirksTools.b ); diff --git a/tests/core/editable/manual/notwantedelementsonselectallandbackspace.html b/tests/core/editable/manual/notwantedelementsonselectallandbackspace.html new file mode 100644 index 00000000000..2b86b23709a --- /dev/null +++ b/tests/core/editable/manual/notwantedelementsonselectallandbackspace.html @@ -0,0 +1,17 @@ +
+

Apollo 11

Source

+
+

Result:

+ + diff --git a/tests/core/editable/manual/notwantedelementsonselectallandbackspace.md b/tests/core/editable/manual/notwantedelementsonselectallandbackspace.md new file mode 100644 index 00000000000..b1377ceb602 --- /dev/null +++ b/tests/core/editable/manual/notwantedelementsonselectallandbackspace.md @@ -0,0 +1,12 @@ +@bender-tags: 4.5.0, tc +@bender-ui: collapsed +@bender-ckeditor-plugins: format, stylescombo, toolbar, wysiwygarea, undo + +---- + +1. Focus editor. +2. Select whole text using shortcut. +3. Press `Backspace` or `Delete`. + +**Expected result:** There should be no `` element in result and bogus `
` should be present. +**Please note:** There might be either an `

` element or a `

` in an editable.