diff --git a/src/view/renderer.js b/src/view/renderer.js index c2fa297bf..f96130075 100644 --- a/src/view/renderer.js +++ b/src/view/renderer.js @@ -381,6 +381,12 @@ export default class Renderer { return false; } + // Prevent adding inline filler inside elements with contenteditable=false. + // https://github.com/ckeditor/ckeditor5-engine/issues/1170 + if ( !_isEditable( selectionParent ) ) { + return false; + } + // We have block filler, we do not need inline one. if ( selectionOffset === selectionParent.getFillerOffset() ) { return false; @@ -708,3 +714,18 @@ export default class Renderer { } mix( Renderer, ObservableMixin ); + +// Checks if provided element is editable. +// +// @private +// @param {module:engine/view/element~Element} element +// @returns {Boolean} +function _isEditable( element ) { + if ( element.getAttribute( 'contenteditable' ) == 'false' ) { + return false; + } + + const parent = element.findAncestor( element => element.hasAttribute( 'contenteditable' ) ); + + return !parent || parent.getAttribute( 'contenteditable' ) == 'true'; +} diff --git a/tests/view/renderer.js b/tests/view/renderer.js index 7fc1ef786..64fb05969 100644 --- a/tests/view/renderer.js +++ b/tests/view/renderer.js @@ -410,6 +410,50 @@ describe( 'Renderer', () => { expect( viewRoot ).to.be.ok; } ); + it( 'should not add filler when inside contenteditable=false parent', () => { + const { view: viewP, selection: newSelection } = parse( + 'foo[]bar' ); + + viewRoot.appendChildren( viewP ); + selection.setTo( newSelection ); + + renderer.markToSync( 'children', viewRoot ); + renderer.render(); + + expect( domRoot.childNodes.length ).to.equal( 1 ); + expect( domRoot.childNodes[ 0 ].tagName.toLowerCase() ).to.equal( 'p' ); + + const domP = domRoot.childNodes[ 0 ]; + + expect( domP.childNodes.length ).to.equal( 3 ); + expect( domP.childNodes[ 0 ].data ).to.equal( 'foo' ); + expect( domP.childNodes[ 2 ].data ).to.equal( 'bar' ); + expect( domP.childNodes[ 1 ].tagName.toLowerCase() ).to.equal( 'b' ); + expect( domP.childNodes[ 1 ].childNodes.length ).to.equal( 0 ); + } ); + + it( 'should not add filler when inside contenteditable=false ancestor', () => { + const { view: viewP, selection: newSelection } = parse( + 'foo[]bar' ); + + viewRoot.appendChildren( viewP ); + selection.setTo( newSelection ); + + renderer.markToSync( 'children', viewRoot ); + renderer.render(); + + expect( domRoot.childNodes.length ).to.equal( 1 ); + expect( domRoot.childNodes[ 0 ].tagName.toLowerCase() ).to.equal( 'p' ); + + const domP = domRoot.childNodes[ 0 ]; + + expect( domP.childNodes.length ).to.equal( 3 ); + expect( domP.childNodes[ 0 ].data ).to.equal( 'foo' ); + expect( domP.childNodes[ 2 ].data ).to.equal( 'bar' ); + expect( domP.childNodes[ 1 ].tagName.toLowerCase() ).to.equal( 'b' ); + expect( domP.childNodes[ 1 ].childNodes.length ).to.equal( 0 ); + } ); + it( 'should add and remove inline filler in case

foo[]bar

', () => { const domSelection = document.getSelection();