Skip to content

Commit 11d9004

Browse files
author
Piotr Jasiun
committed
Merge branch 't/11586b' into major
2 parents f2264f1 + 661429d commit 11d9004

11 files changed

+976
-180
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ New Features:
2727

2828
Fixed Issues:
2929

30+
* [#11586](http://dev.ckeditor.com/ticket/11586): Fixed: [`range.cloneContents()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.range-method-cloneContents) changes DOM and selection.
3031
* [#12018](http://dev.ckeditor.com/ticket/12018): [Nested widgets] Fixed and reviewed: Nested widgets garbage collection.
3132
* [#12024](http://dev.ckeditor.com/ticket/12024): [Nested widgets][FF] Fixed: Outline is extended to the left by unpositioned drag handlers.
3233
* [#12006](http://dev.ckeditor.com/ticket/12006): [Nested widgets] Fixed: Drag and drop of nested block widgets.

bender.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ var config = {
5757
'tests/core/editable/keystrokes/delbackspacequirks/collapsed#test backspace #9': 'env.safari',
5858
'tests/core/editable/keystrokes/delbackspacequirks/collapsed#test backspace, merge #2': 'env.safari',
5959
'tests/core/editable/keystrokes/delbackspacequirks/collapsed#test backspace, merge #3': 'env.safari',
60-
'tests/core/editable/keystrokes/delbackspacequirks/collapsed#test backspace, merge #8': 'env.safari'
60+
'tests/core/editable/keystrokes/delbackspacequirks/collapsed#test backspace, merge #8': 'env.safari',
61+
62+
// IE8-10 (#12964)
63+
'tests/core/editable/getextractselectedhtml#test extractHtmlFromRange: tables #20': 'env.ie && env.version < 11'
6164
}
6265
},
6366

core/dom/range.js

Lines changed: 310 additions & 174 deletions
Large diffs are not rendered by default.

tests/core/dom/range/clonecontents.js

Lines changed: 259 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@
77
doc = CKEDITOR.document,
88
html1 = document.getElementById( 'playground' ).innerHTML;
99

10-
var tests = {
10+
bender.editors = {
11+
classic: {
12+
name: 'classic'
13+
}
14+
};
15+
16+
bender.test( {
1117
setUp: function() {
1218
document.getElementById( 'playground' ).innerHTML = html1;
1319
},
@@ -218,8 +224,257 @@
218224
assert.areSame( document.getElementById( '_Para' ), range.endContainer.$, 'range.endContainer' );
219225
assert.areSame( 3, range.endOffset, 'range.endOffset' );
220226
assert.isFalse( range.collapsed, 'range.collapsed' );
221-
}
222-
};
227+
},
228+
229+
// #11586
230+
'test cloneContents does not split text nodes': function() {
231+
var root = doc.createElement( 'div' ),
232+
range = new CKEDITOR.dom.range( doc );
233+
234+
root.setHtml( 'foo<b>bar</b>' );
235+
doc.getBody().append( root );
236+
237+
var startContainer = root.getFirst(),
238+
endContainer = root.getLast().getFirst();
239+
240+
range.setStart( startContainer, 2 ); // fo[o
241+
range.setEnd( endContainer, 1 ); // b]ar
242+
243+
var clone = range.cloneContents();
244+
245+
assert.areSame( 'foo', root.getFirst().getText(), 'startContainer was not split' );
246+
assert.areSame( 'bar', root.getLast().getFirst().getText(), 'endContainer was not split' );
247+
assert.areSame( startContainer.$, range.startContainer.$, 'startContainer reference' );
248+
assert.areSame( endContainer.$, range.endContainer.$, 'endContainer reference' );
249+
assert.isInnerHtmlMatching( 'o<b>b</b>', clone.getHtml() );
250+
},
251+
252+
// #11586
253+
'test cloneContents does not affect selection': function() {
254+
var editor = bender.editors.classic,
255+
range = editor.createRange(),
256+
editable = editor.editable();
257+
258+
editable.setHtml( '<p>foo<b>bar</b></p>' );
259+
260+
range.setStart( editable.getFirst().getFirst(), 2 ); // fo[o
261+
range.setEnd( editable.getFirst().getLast().getFirst(), 1 ); // b]ar
262+
263+
var sel = editor.getSelection();
264+
sel.selectRanges( [ range ] );
265+
266+
var clone = range.cloneContents();
267+
268+
assert.isInnerHtmlMatching( '<p>fo{o<b>b}ar</b>@</p>', bender.tools.selection.getWithHtml( editor ) );
269+
assert.isInnerHtmlMatching( 'o<b>b</b>', clone.getHtml() );
270+
},
271+
272+
'test cloneContents - empty text node is returned if range is at a text boundary': function() {
273+
var root = doc.createElement( 'div' ),
274+
range = new CKEDITOR.dom.range( doc );
275+
276+
root.setHtml( 'foo<b>bar</b>bom' );
277+
doc.getBody().append( root );
278+
279+
range.setStart( root.getFirst(), 3 ); // foo[
280+
range.setEnd( root.getLast(), 0 ); // ]bom
281+
282+
var clone = range.cloneContents(),
283+
firstChild = clone.getFirst(),
284+
lastChild = clone.getLast();
285+
286+
assert.areSame( CKEDITOR.NODE_TEXT, firstChild.type, 'start is a text node' );
287+
assert.areSame( '', firstChild.getText(), 'start text node is empty' );
223288

224-
bender.test( tests );
289+
assert.areSame( CKEDITOR.NODE_TEXT, lastChild.type, 'end is a text node' );
290+
assert.areSame( '', lastChild.getText(), 'end text node is empty' );
291+
},
292+
293+
'test cloneContents - range inside a single text node': function() {
294+
var root = doc.createElement( 'div' ),
295+
range = new CKEDITOR.dom.range( doc );
296+
297+
root.setHtml( 'bar' );
298+
doc.getBody().append( root );
299+
300+
range.setStart( root.getFirst(), 1 ); // b[ar
301+
range.setEnd( root.getFirst(), 2 ); // ba]r
302+
303+
var clone = range.cloneContents();
304+
305+
assert.areSame( 'bar', root.getFirst().getText(), 'startContainer was not split' );
306+
assert.areSame( 1, root.getChildCount(), '1 child left' );
307+
assert.areSame( 'a', clone.getHtml() );
308+
},
309+
310+
'test cloneContents - element selection preceded by a text node': function() {
311+
var root = doc.createElement( 'div' ),
312+
range = new CKEDITOR.dom.range( doc );
313+
314+
root.setHtml( 'foo<b>bar</b>bom' );
315+
doc.getBody().append( root );
316+
317+
range.setStart( root, 1 ); // [<b>
318+
range.setEnd( root, 2 ); // </b>]
319+
320+
var clone = range.cloneContents();
321+
322+
assert.isInnerHtmlMatching( '<b>bar</b>', clone.getHtml() );
323+
},
324+
325+
'test cloneContents - startOffset == 0, startContainer is element': function() {
326+
var root = doc.createElement( 'div' ),
327+
range = new CKEDITOR.dom.range( doc );
328+
329+
root.setHtml( '<p><b>bar</b>bom</p>' );
330+
doc.getBody().append( root );
331+
332+
range.setStart( root.getFirst(), 0 ); // [<b>
333+
range.setEnd( root.getFirst().getLast(), 2 ); // bo}m
334+
335+
var clone = range.cloneContents();
336+
337+
assert.isInnerHtmlMatching( '<b>bar</b>bo', clone.getHtml() );
338+
},
339+
340+
'test cloneContents - startOffset == childCount, startContainer is element': function() {
341+
var root = doc.createElement( 'div' ),
342+
range = new CKEDITOR.dom.range( doc );
343+
344+
root.setHtml( '<p><b>bar</b></p><p>foo</p>' );
345+
doc.getBody().append( root );
346+
347+
range.setStart( root.getFirst(), 1 ); // </b>[
348+
range.setEnd( root.getLast().getFirst(), 2 ); // fo}o
349+
350+
var clone = range.cloneContents();
351+
352+
assert.isInnerHtmlMatching( '<p></p><p>fo</p>', clone.getHtml() );
353+
},
354+
355+
'test cloneContents - endOffset == 0, endContainer is element': function() {
356+
var root = doc.createElement( 'div' ),
357+
range = new CKEDITOR.dom.range( doc );
358+
359+
root.setHtml( '<p>foo</p><p><b>bar</b></p>' );
360+
doc.getBody().append( root );
361+
362+
range.setStart( root.getFirst().getFirst(), 1 ); // f[oo
363+
range.setEnd( root.getLast(), 0 ); // ]<b>
364+
365+
var clone = range.cloneContents();
366+
367+
assert.isInnerHtmlMatching( '<p>oo</p><p></p>', clone.getHtml() );
368+
},
369+
370+
'test cloneContents - endOffset == childCount, endContainer is element': function() {
371+
var root = doc.createElement( 'div' ),
372+
range = new CKEDITOR.dom.range( doc );
373+
374+
root.setHtml( '<p>foo<b>bar</b></p>' );
375+
doc.getBody().append( root );
376+
377+
range.setStart( root.getFirst().getFirst(), 1 ); // f[oo
378+
range.setEnd( root.getFirst(), 2 ); // </b>]
379+
380+
var clone = range.cloneContents();
381+
382+
assert.isInnerHtmlMatching( 'oo<b>bar</b>', clone.getHtml() );
383+
},
384+
385+
'test cloneContents - offsets = ( 0, childCount ), containers are elements': function() {
386+
var root = doc.createElement( 'div' ),
387+
range = new CKEDITOR.dom.range( doc );
388+
389+
root.setHtml( '<p><b>bar</b></p>' );
390+
doc.getBody().append( root );
391+
392+
range.setStart( root.getFirst(), 0 ); // [<b>
393+
range.setEnd( root.getFirst(), 1 ); // </b>]
394+
395+
var clone = range.cloneContents();
396+
397+
assert.isInnerHtmlMatching( '<b>bar</b>', clone.getHtml() );
398+
},
399+
400+
'test cloneContents - right branch much longer': function() {
401+
var root = doc.createElement( 'div' ),
402+
range = new CKEDITOR.dom.range( doc );
403+
404+
root.setHtml( 'foo<u>x<b>bar<i>bom</i>y</b>z</u>' );
405+
doc.getBody().append( root );
406+
407+
range.setStart( root.getFirst(), 1 ); // f{oo
408+
range.setEnd( root.findOne( 'i' ).getFirst(), 2 ); // bo}m
409+
410+
var clone = range.cloneContents();
411+
412+
assert.isInnerHtmlMatching( 'oo<u>x<b>bar<i>bo</i></b></u>', clone.getHtml() );
413+
},
414+
415+
'test cloneContents - left branch much longer': function() {
416+
var root = doc.createElement( 'div' ),
417+
range = new CKEDITOR.dom.range( doc );
418+
419+
root.setHtml( '<u>x<b>y<i>bom</i>bar</b>z</u>foo' );
420+
doc.getBody().append( root );
421+
422+
range.setStart( root.findOne( 'i' ).getFirst(), 1 ); // b{om
423+
range.setEnd( root.getLast(), 2 ); // fo}o
424+
425+
var clone = range.cloneContents();
426+
427+
assert.isInnerHtmlMatching( '<u><b><i>om</i>bar</b>z</u>fo', clone.getHtml() );
428+
},
429+
430+
'test cloneContents - finding levelClone in the right branch': function() {
431+
var root = doc.createElement( 'div' ),
432+
range = new CKEDITOR.dom.range( doc );
433+
434+
root.setHtml( '<p><b>foo<br>bar</b><i><u>x</u><s>bom</s></i></p>' );
435+
doc.getBody().append( root );
436+
437+
range.setStart( root.findOne( 'b' ).getFirst(), 1 ); // f{oo
438+
range.setEnd( root.findOne( 's' ).getFirst(), 2 ); // bo}m
439+
440+
var clone = range.cloneContents();
441+
442+
assert.isInnerHtmlMatching( '<b>oo<br />bar</b><i><u>x</u><s>bo</s></i>', clone.getHtml() );
443+
},
444+
445+
'test cloneContents - collapsed range': function() {
446+
var root = doc.createElement( 'div' ),
447+
range = new CKEDITOR.dom.range( doc );
448+
449+
root.setHtml( '<p>foo</p>' );
450+
doc.getBody().append( root );
451+
452+
range.setStart( root.findOne( 'p' ).getFirst(), 1 ); // f^oo
453+
range.collapse( true );
454+
455+
var clone = range.cloneContents();
456+
457+
// Nothing should happens when range is collapsed.
458+
assert.areSame( '', clone.getHtml() );
459+
assert.isInnerHtmlMatching( '<p>foo</p>', root.getHtml() );
460+
assert.areSame( root.findOne( 'p' ).getFirst(), range.startContainer, 'range.startContainer' );
461+
assert.areSame( 1, range.startOffset, 'range.startOffset' );
462+
assert.isTrue( range.collapsed, 'range.collapsed' );
463+
},
464+
465+
'test cloneContents - empty containers': function() {
466+
var root = doc.createElement( 'div' ),
467+
range = new CKEDITOR.dom.range( doc );
468+
469+
root.setHtml( 'x<h1></h1><p>foo</p><h2></h2>y' );
470+
doc.getBody().append( root );
471+
472+
range.setStart( root.findOne( 'h1' ), 0 ); // <h1>[</h1>
473+
range.setEnd( root.findOne( 'h2' ), 0 ); // <h2>]</h2>
474+
475+
var clone = range.cloneContents();
476+
477+
assert.isInnerHtmlMatching( '<h1></h1><p>foo</p><h2></h2>', clone.getHtml() );
478+
}
479+
} );
225480
} )();

tests/core/dom/range/extractcontents.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,80 @@
255255

256256
assert.isInnerHtmlMatching( '<p><b>f</b>[]<u>r</u></p>', bender.tools.range.getWithHtml( root, range ) );
257257
assert.isInnerHtmlMatching( '<b>oo</b>xxx<u>ba</u>', clone.getHtml() );
258+
},
259+
260+
'test extractContents - collapsed range': function() {
261+
var root = doc.createElement( 'div' ),
262+
range = new CKEDITOR.dom.range( doc );
263+
264+
root.setHtml( '<p>foo</p>' );
265+
doc.getBody().append( root );
266+
267+
range.setStart( root.findOne( 'p' ).getFirst(), 1 ); // f^oo
268+
range.collapse( true );
269+
270+
var clone = range.extractContents();
271+
272+
// Nothing should happens when range is collapsed.
273+
assert.areSame( '', clone.getHtml() );
274+
assert.isInnerHtmlMatching( '<p>foo</p>', root.getHtml() );
275+
assert.areSame( root.findOne( 'p' ).getFirst(), range.startContainer, 'range.startContainer' );
276+
assert.areSame( 1, range.startOffset, 'range.startOffset' );
277+
assert.isTrue( range.collapsed, 'range.collapsed' );
278+
},
279+
280+
'test extractContents - empty containers': function() {
281+
var root = doc.createElement( 'div' ),
282+
range = new CKEDITOR.dom.range( doc );
283+
284+
root.setHtml( 'x<h1></h1><p>foo</p><h2></h2>y' );
285+
doc.getBody().append( root );
286+
287+
range.setStart( root.findOne( 'h1' ), 0 ); // <h1>[</h1>
288+
range.setEnd( root.findOne( 'h2' ), 0 ); // <h2>]</h2>
289+
290+
var clone = range.extractContents();
291+
292+
assert.isInnerHtmlMatching( '<h1></h1><p>foo</p><h2></h2>', clone.getHtml() );
293+
assert.isInnerHtmlMatching( 'x<h1></h1>[]<h2></h2>y', bender.tools.range.getWithHtml( root, range ) );
294+
},
295+
296+
'test extractContents - empty containers at different level': function() {
297+
var root = doc.createElement( 'div' ),
298+
range = new CKEDITOR.dom.range( doc );
299+
300+
root.setHtml( 'x<div><h1></h1></div><p>foo</p><div>x</div>y' );
301+
doc.getBody().append( root );
302+
303+
range.setStart( root.findOne( 'h1' ), 0 ); // <h1>[</h1>
304+
range.setEnd( root.getChild( 3 ), 0 ); // <div>]x</div>
305+
306+
var clone = range.extractContents();
307+
308+
assert.isInnerHtmlMatching( '<div><h1></h1></div><p>foo</p><div></div>', clone.getHtml() );
309+
assert.isInnerHtmlMatching( 'x<div><h1></h1></div>[]<div>x</div>y', bender.tools.range.getWithHtml( root, range ) );
310+
},
311+
312+
'test extractContents - empty containers with mergeThen': function() {
313+
// IE8 has problems with empty inline nodes as usual.
314+
if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) {
315+
assert.ignore();
316+
}
317+
318+
var root = doc.createElement( 'div' ),
319+
range = new CKEDITOR.dom.range( doc );
320+
321+
root.setHtml( 'x<b></b><p>foo</p><b>a</b>y' );
322+
doc.getBody().append( root );
323+
324+
range.setStart( root.getChild( 1 ), 0 ); // <b>[</b>
325+
range.setEnd( root.getChild( 3 ), 0 ); // <b>]x</b>
326+
327+
var clone = range.extractContents( true );
328+
329+
// We would lost empty inline elements so we add "*".
330+
assert.isInnerHtmlMatching( '<b>*</b><p>foo</p><b>*</b>', clone.getHtml().replace( /<b><\/b>/g, '<b>*</b>' ) );
331+
assert.isInnerHtmlMatching( 'x<b>[]a</b>y', bender.tools.range.getWithHtml( root, range ).replace( /<b><\/b>/g, '<b>*</b>' ) );
258332
}
259333
};
260334

0 commit comments

Comments
 (0)