Skip to content

Commit a3b2b66

Browse files
committed
Merge branch 't/14407' into major
2 parents 56e6fda + 324d696 commit a3b2b66

File tree

5 files changed

+124
-1
lines changed

5 files changed

+124
-1
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Fixed Issues:
2424
* [#14894](https://dev.ckeditor.com/ticket/14894): [Chrome] Fixed: Editor scrolls to top after focusing or when a dialog is opened.
2525
* [#14769](https://dev.ckeditor.com/ticket/14769): Fixed: URLs with '-' in host are not detected by [Autolink](http://ckeditor.com/addon/autolink) plugin.
2626
* [#16804](https://dev.ckeditor.com/ticket/16804): Fixed: Focus is not on the first menu item when user opens [Context Menu](http://ckeditor.com/addon/contextmenu) or combobox from editor's toolbar.
27+
* [#14407](https://dev.ckeditor.com/ticket/14407): [IE] Fixed: Non-editable widgets can be edited.
2728

2829
## CKEditor 4.6.2
2930

core/selection.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,14 @@
704704
// 2. After the accomplish of keyboard and mouse events.
705705
editable.attachListener( editable, 'selectionchange', checkSelectionChange, editor );
706706
editable.attachListener( editable, 'keyup', checkSelectionChangeTimeout, editor );
707+
// #14407 - Don't even let anything happen if the selection is in a non-editable element.
708+
editable.attachListener( editable, 'keydown', function( evt ) {
709+
var sel = this.getSelection( 1 );
710+
if ( nonEditableAscendant( sel ) ) {
711+
sel.selectElement( nonEditableAscendant( sel ) );
712+
evt.data.preventDefault();
713+
}
714+
}, editor );
707715
// Always fire the selection change on focus gain.
708716
// On Webkit do this on DOMFocusIn, because the selection is unlocked on it too and
709717
// we need synchronization between those listeners to not lost cached editor._.previousActive property
@@ -788,6 +796,18 @@
788796
if ( sel.type != 'None' && range.parentElement().ownerDocument == doc.$ )
789797
range.select();
790798
}
799+
800+
function nonEditableAscendant( sel ) {
801+
if ( CKEDITOR.env.ie ) {
802+
var range = sel.getRanges()[ 0 ],
803+
ascendant = range ? range.startContainer.getAscendant( function( parent ) {
804+
return parent.type == CKEDITOR.NODE_ELEMENT &&
805+
( parent.getAttribute( 'contenteditable' ) == 'false' || parent.getAttribute( 'contenteditable' ) == 'true' );
806+
}, true ) : null ;
807+
808+
return range && ascendant.getAttribute( 'contenteditable' ) == 'false' && ascendant;
809+
}
810+
}
791811
} );
792812

793813
editor.on( 'setData', function() {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
<textarea name="editor1" id="editor1" cols="30" rows="10">
3+
<p>Put caret here first.</p>
4+
</textarea>
5+
6+
<script>
7+
8+
CKEDITOR.plugins.add( 'noneditablewidget', {
9+
requires: 'widget',
10+
init: function ( editor ) {
11+
editor.widgets.add( 'noneditablewidget', {
12+
button: 'Non-editable widget',
13+
order: 1,
14+
template: '<div class="noneditable">' +
15+
'<div>This part shouldn\'t be editable.</div>' +
16+
'</div>',
17+
allowedContent: 'div(noneditable)',
18+
requiredContent: 'div',
19+
upcast: function( element ) {
20+
return element.name == 'div' && element.hasClass( 'noneditable' );
21+
}
22+
} );
23+
}
24+
} );
25+
26+
if ( !CKEDITOR.env.ie ) {
27+
bender.ignore();
28+
}
29+
30+
CKEDITOR.replace( 'editor1', {
31+
extraPlugins: 'noneditablewidget',
32+
extraAllowedContent: 'div'
33+
} );
34+
35+
36+
</script>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
@bender-tags: tc, 4.7.0, 14407, selection, widget
2+
@bender-ui: collapsed
3+
@bender-ckeditor-plugins: wysiwygarea, toolbar, sourcearea, elementspath
4+
5+
## Test scenario
6+
7+
1. There's an empty button at the start of the second row in the toolbar - use it to add a non-editable widget
8+
above the paragraph already present there.
9+
2. Put the caret at the start of the paragraph and press the `up arrow` key.
10+
11+
** Alternatively: **
12+
1. Create the non-editable widget.
13+
2. Make the editor lose focus.
14+
3. Place the cursor over the widget so that its icon is `text` and its upper-left corner is exactly at the dot in "editable."
15+
4. Click to try to place the caret inside the editable or drag the mouse to create a range.
16+
17+
## Expected
18+
19+
The selection is inside the non-editable area, but once you press any key the whole widget gets selected.
20+
21+
## Unexpected
22+
23+
The widget is not selected when you press a key.

tests/core/selection/selection.js

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -652,5 +652,48 @@ bender.test( {
652652
sel.getType();
653653

654654
assert.areSame( initialRev, sel.rev, 'Revision has not been modified' );
655+
},
656+
657+
'test IE editable contenteditable="false" handling 1': function() {
658+
if ( !CKEDITOR.env.ie ) {
659+
assert.ignore();
660+
}
661+
var preventSpy = sinon.spy();
662+
663+
bender.tools.setHtmlWithSelection( this.editor, '<span contenteditable="false">^bar</span>' );
664+
665+
var selection = this.editor.getSelection(),
666+
range = selection.getRanges()[ 0 ];
667+
668+
// Selection was moved somehow.
669+
if ( range && range.startContainer.getName() !== 'span' ) {
670+
assert.ignore();
671+
}
672+
673+
this.editor.editable().fire( 'keydown', {
674+
preventDefault: preventSpy,
675+
getKeystroke: function() {},
676+
getKey: function() {}
677+
} );
678+
679+
assert.isTrue( preventSpy.called, 'preventDefault() on keydown was called' );
680+
},
681+
682+
'test IE editable contenteditable="false" handling 2': function() {
683+
if ( !CKEDITOR.env.ie ) {
684+
assert.ignore();
685+
}
686+
var preventSpy = sinon.spy();
687+
688+
bender.tools.setHtmlWithSelection( this.editor, '<span contenteditable="false">' +
689+
'<span contenteditable="true">^bar</span></span>' );
690+
691+
this.editor.editable().fire( 'keydown', {
692+
preventDefault: preventSpy,
693+
getKeystroke: function() {},
694+
getKey: function() {}
695+
} );
696+
697+
assert.isFalse( preventSpy.called, 'preventDefault() on keydown was called' );
655698
}
656-
} );
699+
} );

0 commit comments

Comments
 (0)