1 /*
  2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
  3 For licensing, see LICENSE.html or http://ckeditor.com/license
  4 */
  5
  6 CKEDITOR.dom.range = function( document )
  7 {
  8 	this.startContainer	= null;
  9 	this.startOffset	= null;
 10 	this.endContainer	= null;
 11 	this.endOffset		= null;
 12 	this.collapsed		= true;
 13
 14 	this.document = document;
 15 };
 16
 17 (function()
 18 {
 19 	// Updates the "collapsed" property for the given range object.
 20 	var updateCollapsed = function( range )
 21 	{
 22 		range.collapsed = (
 23 			range.startContainer &&
 24 			range.endContainer &&
 25 			range.startContainer.equals( range.endContainer ) &&
 26 			range.startOffset == range.endOffset );
 27 	};
 28
 29 	// This is a shared function used to delete, extract and clone the range
 30 	// contents.
 31 	// V2
 32 	var execContentsAction = function( range, action, docFrag )
 33 	{
 34 		range.optimizeBookmark();
 35
 36 		var startNode	= range.startContainer;
 37 		var endNode		= range.endContainer;
 38
 39 		var startOffset	= range.startOffset;
 40 		var endOffset	= range.endOffset;
 41
 42 		var removeStartNode;
 43 		var removeEndNode;
 44
 45 		// For text containers, we must simply split the node and point to the
 46 		// second part. The removal will be handled by the rest of the code .
 47 		if ( endNode.type == CKEDITOR.NODE_TEXT )
 48 			endNode = endNode.split( endOffset );
 49 		else
 50 		{
 51 			// If the end container has children and the offset is pointing
 52 			// to a child, then we should start from it.
 53 			if ( endNode.getChildCount() > 0 )
 54 			{
 55 				// If the offset points after the last node.
 56 				if ( endOffset >= endNode.getChildCount() )
 57 				{
 58 					// Let's create a temporary node and mark it for removal.
 59 					endNode = endNode.append( range.document.createText( '' ) );
 60 					removeEndNode = true;
 61 				}
 62 				else
 63 					endNode = endNode.getChild( endOffset );
 64 			}
 65 		}
 66
 67 		// For text containers, we must simply split the node. The removal will
 68 		// be handled by the rest of the code .
 69 		if ( startNode.type == CKEDITOR.NODE_TEXT )
 70 		{
 71 			startNode.split( startOffset );
 72
 73 			// In cases the end node is the same as the start node, the above
 74 			// splitting will also split the end, so me must move the end to
 75 			// the second part of the split.
 76 			if ( startNode.equals( endNode ) )
 77 				endNode = startNode.getNext();
 78 		}
 79 		else
 80 		{
 81 			// If the start container has children and the offset is pointing
 82 			// to a child, then we should start from its previous sibling.
 83
 84 			// If the offset points to the first node, we don't have a
 85 			// sibling, so let's use the first one, but mark it for removal.
 86 			if ( !startOffset )
 87 			{
 88 				// Let's create a temporary node and mark it for removal.
 89 				startNode = startNode.getFirst().insertBeforeMe( range.document.createText( '' ) );
 90 				removeStartNode = true;
 91 			}
 92 			else if ( startOffset >= startNode.getChildCount() )
 93 			{
 94 				// Let's create a temporary node and mark it for removal.
 95 				startNode = startNode.append( range.document.createText( '' ) );
 96 				removeStartNode = true;
 97 			}
 98 			else
 99 				startNode = startNode.getChild( startOffset ).getPrevious();
100 		}
101
102 		// Get the parent nodes tree for the start and end boundaries.
103 		var startParents	= startNode.getParents();
104 		var endParents		= endNode.getParents();
105
106 		// Compare them, to find the top most siblings.
107 		var i, topStart, topEnd;
108
109 		for ( i = 0 ; i < startParents.length ; i++ )
110 		{
111 			topStart = startParents[ i ];
112 			topEnd = endParents[ i ];
113
114 			// The compared nodes will match until we find the top most
115 			// siblings (different nodes that have the same parent).
116 			// "i" will hold the index in the parents array for the top
117 			// most element.
118 			if ( !topStart.equals( topEnd ) )
119 				break;
120 		}
121
122 		var clone = docFrag, levelStartNode, levelClone, currentNode, currentSibling;
123
124 		// Remove all successive sibling nodes for every node in the
125 		// startParents tree.
126 		for ( var j = i ; j < startParents.length ; j++ )
127 		{
128 			levelStartNode = startParents[j];
129
130 			// For Extract and Clone, we must clone this level.
131 			if ( clone && !levelStartNode.equals( startNode ) )		// action = 0 = Delete
132 				levelClone = clone.append( levelStartNode.clone() );
133
134 			currentNode = levelStartNode.getNext();
135
136 			while( currentNode )
137 			{
138 				// Stop processing when the current node matches a node in the
139 				// endParents tree or if it is the endNode.
140 				if ( currentNode.equals( endParents[ j ] ) || currentNode.equals( endNode ) )
141 					break;
142
143 				// Cache the next sibling.
144 				currentSibling = currentNode.getNext();
145
146 				// If cloning, just clone it.
147 				if ( action == 2 )	// 2 = Clone
148 					clone.append( currentNode.clone( true ) );
149 				else
150 				{
151 					// Both Delete and Extract will remove the node.
152 					currentNode.remove();
153
154 					// When Extracting, move the removed node to the docFrag.
155 					if ( action == 1 )	// 1 = Extract
156 						clone.append( currentNode );
157 				}
158
159 				currentNode = currentSibling;
160 			}
161
162 			if ( clone )
163 				clone = levelClone;
164 		}
165
166 		clone = docFrag;
167
168 		// Remove all previous sibling nodes for every node in the
169 		// endParents tree.
170 		for ( var k = i ; k < endParents.length ; k++ )
171 		{
172 			levelStartNode = endParents[ k ];
173
174 			// For Extract and Clone, we must clone this level.
175 			if ( action > 0 && !levelStartNode.equals( endNode ) )		// action = 0 = Delete
176 				levelClone = clone.append( levelStartNode.clone() );
177
178 			// The processing of siblings may have already been done by the parent.
179 			if ( !startParents[ k ] || levelStartNode.$.parentNode != startParents[ k ].$.parentNode )
180 			{
181 				currentNode = levelStartNode.getPrevious();
182
183 				while( currentNode )
184 				{
185 					// Stop processing when the current node matches a node in the
186 					// startParents tree or if it is the startNode.
187 					if ( currentNode.equals( startParents[ k ] ) || currentNode.equals( startNode ) )
188 						break;
189
190 					// Cache the next sibling.
191 					currentSibling = currentNode.getPrevious();
192
193 					// If cloning, just clone it.
194 					if ( action == 2 )	// 2 = Clone
195 						clone.$.insertBefore( currentNode.$.cloneNode( true ), clone.$.firstChild ) ;
196 					else
197 					{
198 						// Both Delete and Extract will remove the node.
199 						currentNode.remove();
200
201 						// When Extracting, mode the removed node to the docFrag.
202 						if ( action == 1 )	// 1 = Extract
203 							clone.$.insertBefore( currentNode.$, clone.$.firstChild );
204 					}
205
206 					currentNode = currentSibling;
207 				}
208 			}
209
210 			if ( clone )
211 				clone = levelClone;
212 		}
213
214 		if ( action == 2 )		// 2 = Clone.
215 		{
216 			// No changes in the DOM should be done, so fix the split text (if any).
217
218 			var startTextNode = range.startContainer;
219 			if ( startTextNode.type == CKEDITOR.NODE_TEXT )
220 			{
221 				startTextNode.$.data += startTextNode.$.nextSibling.data;
222 				startTextNode.$.parentNode.removeChild( startTextNode.$.nextSibling );
223 			}
224
225 			var endTextNode = range.endContainer;
226 			if ( endTextNode.type == CKEDITOR.NODE_TEXT && endTextNode.$.nextSibling )
227 			{
228 				endTextNode.$.data += endTextNode.$.nextSibling.data;
229 				endTextNode.$.parentNode.removeChild( endTextNode.$.nextSibling );
230 			}
231 		}
232 		else
233 		{
234 			// Collapse the range.
235
236 			// If a node has been partially selected, collapse the range between
237 			// topStart and topEnd. Otherwise, simply collapse it to the start. (W3C specs).
238 			if ( topStart && topEnd && ( startNode.$.parentNode != topStart.$.parentNode || endNode.$.parentNode != topEnd.$.parentNode ) )
239 			{
240 				var endIndex = topEnd.getIndex();
241
242 				// If the start node is to be removed, we must correct the
243 				// index to reflect the removal.
244 				if ( removeStartNode && topEnd.$.parentNode == startNode.$.parentNode )
245 					endIndex--;
246
247 				range.setStart( topEnd.getParent(), endIndex );
248 			}
249
250 			// Collapse it to the start.
251 			range.collapse( true );
252 		}
253
254 		// Cleanup any marked node.
255 		if( removeStartNode )
256 			startNode.remove();
257
258 		if( removeEndNode && endNode.$.parentNode )
259 			endNode.remove();
260 	};
261
262 	var inlineChildReqElements = { abbr:1,acronym:1,b:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,q:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,tt:1,u:1,'var':1 };
263
264 	// Creates the appropriate node evaluator for the dom walker used inside
265 	// check(Start|End)OfBlock.
266 	function getCheckStartEndBlockEvalFunction( isStart )
267 	{
268 		var hadBr = false, bookmarkEvaluator = CKEDITOR.dom.walker.bookmark( true );
269 		return function( node )
270 		{
271 			// First ignore bookmark nodes.
272 			if ( bookmarkEvaluator( node ) )
273 				return true;
274
275 			if ( node.type == CKEDITOR.NODE_TEXT )
276 			{
277 				// If there's any visible text, then we're not at the start.
278 				if ( CKEDITOR.tools.trim( node.getText() ).length )
279 					return false;
280 				}
281 			else
282 			{
283 				// If there are non-empty inline elements (e.g. <img />), then we're not
284 				// at the start.
285 				if ( !inlineChildReqElements[ node.getName() ] )
286 				{
287 					// If we're working at the end-of-block, forgive the first <br /> in non-IE
288 					// browsers.
289 					if ( !isStart && !CKEDITOR.env.ie && node.getName() == 'br' && !hadBr )
290 						hadBr = true;
291 					else
292 						return false;
293 				}
294 			}
295 			return true;
296 		};
297 	}
298
299 	// Evaluator for CKEDITOR.dom.element::checkBoundaryOfElement, reject any
300 	// text node and non-empty elements unless it's being bookmark text.
301 	function elementBoundaryEval( node )
302 	{
303 		// Reject any text node unless it's being bookmark.
304 		return node.type != CKEDITOR.NODE_TEXT
305 		       && node.getName() in CKEDITOR.dtd.$removeEmpty
306 			   || node.getParent().hasAttribute( '_fck_bookmark' );
307 	}
308
309 	CKEDITOR.dom.range.prototype =
310 	{
311 		clone : function()
312 		{
313 			var clone = new CKEDITOR.dom.range( this.document );
314
315 			clone.startContainer = this.startContainer;
316 			clone.startOffset = this.startOffset;
317 			clone.endContainer = this.endContainer;
318 			clone.endOffset = this.endOffset;
319 			clone.collapsed = this.collapsed;
320
321 			return clone;
322 		},
323
324 		collapse : function( toStart )
325 		{
326 			if ( toStart )
327 			{
328 				this.endContainer	= this.startContainer;
329 				this.endOffset		= this.startOffset;
330 			}
331 			else
332 			{
333 				this.startContainer	= this.endContainer;
334 				this.startOffset	= this.endOffset;
335 			}
336
337 			this.collapsed = true;
338 		},
339
340 		// The selection may be lost when cloning (due to the splitText() call).
341 		cloneContents : function()
342 		{
343 			var docFrag = new CKEDITOR.dom.documentFragment( this.document );
344
345 			if ( !this.collapsed )
346 				execContentsAction( this, 2, docFrag );
347
348 			return docFrag;
349 		},
350
351 		deleteContents : function()
352 		{
353 			if ( this.collapsed )
354 				return;
355
356 			execContentsAction( this, 0 );
357 		},
358
359 		extractContents : function()
360 		{
361 			var docFrag = new CKEDITOR.dom.documentFragment( this.document );
362
363 			if ( !this.collapsed )
364 				execContentsAction( this, 1, docFrag );
365
366 			return docFrag;
367 		},
368
369 		/**
370 		 * Creates a bookmark object, which can be later used to restore the
371 		 * range by using the moveToBookmark function.
372 		 * This is an "intrusive" way to create a bookmark. It includes <span> tags
373 		 * in the range boundaries. The advantage of it is that it is possible to
374 		 * handle DOM mutations when moving back to the bookmark.
375 		 * Attention: the inclusion of nodes in the DOM is a design choice and
376 		 * should not be changed as there are other points in the code that may be
377 		 * using those nodes to perform operations. See GetBookmarkNode.
378 		 * @param {Boolean} [serializable] Indicates that the bookmark nodes
379 		 *		must contain ids, which can be used to restore the range even
380 		 *		when these nodes suffer mutations (like a clonation or innerHTML
381 		 *		change).
382 		 * @returns {Object} And object representing a bookmark.
383 		 */
384 		createBookmark : function( serializable )
385 		{
386 			var startNode, endNode;
387 			var baseId;
388 			var clone;
389
390 			startNode = this.document.createElement( 'span' );
391 			startNode.setAttribute( '_fck_bookmark', 1 );
392 			startNode.setStyle( 'display', 'none' );
393
394 			// For IE, it must have something inside, otherwise it may be
395 			// removed during DOM operations.
396 			startNode.setHtml( ' ' );
397
398 			if ( serializable )
399 			{
400 				baseId = 'cke_bm_' + CKEDITOR.tools.getNextNumber();
401 				startNode.setAttribute( 'id', baseId + 'S' );
402 			}
403
404 			// If collapsed, the endNode will not be created.
405 			if ( !this.collapsed )
406 			{
407 				endNode = startNode.clone();
408 				endNode.setHtml( ' ' );
409
410 				if ( serializable )
411 					endNode.setAttribute( 'id', baseId + 'E' );
412
413 				clone = this.clone();
414 				clone.collapse();
415 				clone.insertNode( endNode );
416 			}
417
418 			clone = this.clone();
419 			clone.collapse( true );
420 			clone.insertNode( startNode );
421
422 			// Update the range position.
423 			if ( endNode )
424 			{
425 				this.setStartAfter( startNode );
426 				this.setEndBefore( endNode );
427 			}
428 			else
429 				this.moveToPosition( startNode, CKEDITOR.POSITION_AFTER_END );
430
431 			return {
432 				startNode : serializable ? baseId + 'S' : startNode,
433 				endNode : serializable ? baseId + 'E' : endNode,
434 				serializable : serializable
435 			};
436 		},
437
438 		/**
439 		 * Creates a "non intrusive" and "mutation sensible" bookmark. This
440 		 * kind of bookmark should be used only when the DOM is supposed to
441 		 * remain stable after its creation.
442 		 * @param {Boolean} [normalized] Indicates that the bookmark must
443 		 *		normalized. When normalized, the successive text nodes are
444 		 *		considered a single node. To sucessful load a normalized
445 		 *		bookmark, the DOM tree must be also normalized before calling
446 		 *		moveToBookmark.
447 		 * @returns {Object} An object representing the bookmark.
448 		 */
449 		createBookmark2 : function( normalized )
450 		{
451 			var startContainer	= this.startContainer,
452 				endContainer	= this.endContainer;
453
454 			var startOffset	= this.startOffset,
455 				endOffset	= this.endOffset;
456
457 			var child, previous;
458
459 			// If there is no range then get out of here.
460 			// It happens on initial load in Safari #962 and if the editor it's
461 			// hidden also in Firefox
462 			if ( !startContainer || !endContainer )
463 				return { start : 0, end : 0 };
464
465 			if ( normalized )
466 			{
467 				// Find out if the start is pointing to a text node that will
468 				// be normalized.
469 				if ( startContainer.type == CKEDITOR.NODE_ELEMENT )
470 				{
471 					child = startContainer.getChild( startOffset );
472
473 					// In this case, move the start information to that text
474 					// node.
475 					if ( child && child.type == CKEDITOR.NODE_TEXT
476 							&& startOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT )
477 					{
478 						startContainer = child;
479 						startOffset = 0;
480 					}
481 				}
482
483 				// Normalize the start.
484 				while ( startContainer.type == CKEDITOR.NODE_TEXT
485 						&& ( previous = startContainer.getPrevious() )
486 						&& previous.type == CKEDITOR.NODE_TEXT )
487 				{
488 					startContainer = previous;
489 					startOffset += previous.getLength();
490 				}
491
492 				// Process the end only if not normalized.
493 				if ( !this.isCollapsed )
494 				{
495 					// Find out if the start is pointing to a text node that
496 					// will be normalized.
497 					if ( endContainer.type == CKEDITOR.NODE_ELEMENT )
498 					{
499 						child = endContainer.getChild( endOffset );
500
501 						// In this case, move the start information to that
502 						// text node.
503 						if ( child && child.type == CKEDITOR.NODE_TEXT
504 								&& endOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT )
505 						{
506 							endContainer = child;
507 							endOffset = 0;
508 						}
509 					}
510
511 					// Normalize the end.
512 					while ( endContainer.type == CKEDITOR.NODE_TEXT
513 							&& ( previous = endContainer.getPrevious() )
514 							&& previous.type == CKEDITOR.NODE_TEXT )
515 					{
516 						endContainer = previous;
517 						endOffset += previous.getLength();
518 					}
519 				}
520 			}
521
522 			return {
523 				start		: startContainer.getAddress( normalized ),
524 				end			: this.isCollapsed ? null : endContainer.getAddress( normalized ),
525 				startOffset	: startOffset,
526 				endOffset	: endOffset,
527 				normalized	: normalized,
528 				is2			: true		// It's a createBookmark2 bookmark.
529 			};
530 		},
531
532 		moveToBookmark : function( bookmark )
533 		{
534 			if ( bookmark.is2 )		// Created with createBookmark2().
535 			{
536 				// Get the start information.
537 				var startContainer	= this.document.getByAddress( bookmark.start, bookmark.normalized ),
538 					startOffset	= bookmark.startOffset;
539
540 				// Get the end information.
541 				var endContainer	= bookmark.end && this.document.getByAddress( bookmark.end, bookmark.normalized ),
542 					endOffset	= bookmark.endOffset;
543
544 				// Set the start boundary.
545 				this.setStart( startContainer, startOffset );
546
547 				// Set the end boundary. If not available, collapse it.
548 				if ( endContainer )
549 					this.setEnd( endContainer, endOffset );
550 				else
551 					this.collapse( true );
552 			}
553 			else					// Created with createBookmark().
554 			{
555 				var serializable = bookmark.serializable,
556 					startNode	= serializable ? this.document.getById( bookmark.startNode ) : bookmark.startNode,
557 					endNode		= serializable ? this.document.getById( bookmark.endNode ) : bookmark.endNode;
558
559 				// Set the range start at the bookmark start node position.
560 				this.setStartBefore( startNode );
561
562 				// Remove it, because it may interfere in the setEndBefore call.
563 				startNode.remove();
564
565 				// Set the range end at the bookmark end node position, or simply
566 				// collapse it if it is not available.
567 				if ( endNode )
568 				{
569 					this.setEndBefore( endNode );
570 					endNode.remove();
571 				}
572 				else
573 					this.collapse( true );
574 			}
575 		},
576
577 		getBoundaryNodes : function()
578 		{
579 			var startNode = this.startContainer,
580 				endNode = this.endContainer,
581 				startOffset = this.startOffset,
582 				endOffset = this.endOffset,
583 				childCount;
584
585 			if ( startNode.type == CKEDITOR.NODE_ELEMENT )
586 			{
587 				childCount = startNode.getChildCount();
588 				if ( childCount > startOffset )
589 					startNode = startNode.getChild( startOffset );
590 				else if ( childCount < 1 )
591 					startNode = startNode.getPreviousSourceNode();
592 				else		// startOffset > childCount but childCount is not 0
593 				{
594 					// Try to take the node just after the current position.
595 					startNode = startNode.$;
596 					while ( startNode.lastChild )
597 						startNode = startNode.lastChild;
598 					startNode = new CKEDITOR.dom.node( startNode );
599
600 					// Normally we should take the next node in DFS order. But it
601 					// is also possible that we've already reached the end of
602 					// document.
603 					startNode = startNode.getNextSourceNode() || startNode;
604 				}
605 			}
606 			if ( endNode.type == CKEDITOR.NODE_ELEMENT )
607 			{
608 				childCount = endNode.getChildCount();
609 				if ( childCount > endOffset )
610 					endNode = endNode.getChild( endOffset ).getPreviousSourceNode( true );
611 				else if ( childCount < 1 )
612 					endNode = endNode.getPreviousSourceNode();
613 				else		// endOffset > childCount but childCount is not 0
614 				{
615 					// Try to take the node just before the current position.
616 					endNode = endNode.$;
617 					while ( endNode.lastChild )
618 						endNode = endNode.lastChild;
619 					endNode = new CKEDITOR.dom.node( endNode );
620 				}
621 			}
622
623 			// Sometimes the endNode will come right before startNode for collapsed
624 			// ranges. Fix it. (#3780)
625 			if ( startNode.getPosition( endNode ) & CKEDITOR.POSITION_FOLLOWING )
626 				startNode = endNode;
627
628 			return { startNode : startNode, endNode : endNode };
629 		},
630
631 		/**
632 		 * Find the node which fully contains the range.
633 		 * @param includeSelf
634 		 * @param {Boolean} ignoreTextNode Whether ignore CKEDITOR.NODE_TEXT type.
635 		 */
636 		getCommonAncestor : function( includeSelf , ignoreTextNode )
637 		{
638 			var start = this.startContainer,
639 				end = this.endContainer,
640 				ancestor;
641
642 			if ( start.equals( end ) )
643 			{
644 				if ( includeSelf
645 						&& start.type == CKEDITOR.NODE_ELEMENT
646 						&& this.startOffset == this.endOffset - 1 )
647 					ancestor = start.getChild( this.startOffset );
648 				else
649 					ancestor = start;
650 			}
651 			else
652 				ancestor = start.getCommonAncestor( end );
653
654 			return ignoreTextNode && !ancestor.is ? ancestor.getParent() : ancestor;
655 		},
656
657 		/**
658 		 * Transforms the startContainer and endContainer properties from text
659 		 * nodes to element nodes, whenever possible. This is actually possible
660 		 * if either of the boundary containers point to a text node, and its
661 		 * offset is set to zero, or after the last char in the node.
662 		 */
663 		optimize : function()
664 		{
665 			var container = this.startContainer;
666 			var offset = this.startOffset;
667
668 			if ( container.type != CKEDITOR.NODE_ELEMENT )
669 			{
670 				if ( !offset )
671 					this.setStartBefore( container );
672 				else if ( offset >= container.getLength() )
673 					this.setStartAfter( container );
674 			}
675
676 			container = this.endContainer;
677 			offset = this.endOffset;
678
679 			if ( container.type != CKEDITOR.NODE_ELEMENT )
680 			{
681 				if ( !offset )
682 					this.setEndBefore( container );
683 				else if ( offset >= container.getLength() )
684 					this.setEndAfter( container );
685 			}
686 		},
687
688 		/**
689 		 * Move the range out of bookmark nodes if they're been the container.
690 		 */
691 		optimizeBookmark: function()
692 		{
693 			var startNode = this.startContainer,
694 				endNode = this.endContainer;
695
696 			if ( startNode.is && startNode.is( 'span' )
697 				&& startNode.hasAttribute( '_fck_bookmark' ) )
698 				this.setStartAt( startNode, CKEDITOR.POSITION_BEFORE_START );
699 			if ( endNode && endNode.is && endNode.is( 'span' )
700 				&& endNode.hasAttribute( '_fck_bookmark' ) )
701 				this.setEndAt( endNode,  CKEDITOR.POSITION_AFTER_END );
702 		},
703
704 		trim : function( ignoreStart, ignoreEnd )
705 		{
706 			var startContainer = this.startContainer,
707 				startOffset = this.startOffset,
708 				collapsed = this.collapsed;
709 			if ( ( !ignoreStart || collapsed )
710 				 && startContainer && startContainer.type == CKEDITOR.NODE_TEXT )
711 			{
712 				// If the offset is zero, we just insert the new node before
713 				// the start.
714 				if ( !startOffset )
715 				{
716 					startOffset = startContainer.getIndex();
717 					startContainer = startContainer.getParent();
718 				}
719 				// If the offset is at the end, we'll insert it after the text
720 				// node.
721 				else if ( startOffset >= startContainer.getLength() )
722 				{
723 					startOffset = startContainer.getIndex() + 1;
724 					startContainer = startContainer.getParent();
725 				}
726 				// In other case, we split the text node and insert the new
727 				// node at the split point.
728 				else
729 				{
730 					var nextText = startContainer.split( startOffset );
731
732 					startOffset = startContainer.getIndex() + 1;
733 					startContainer = startContainer.getParent();
734 					// Check if it is necessary to update the end boundary.
735 					if ( !collapsed && this.startContainer.equals( this.endContainer ) )
736 						this.setEnd( nextText, this.endOffset - this.startOffset );
737 				}
738
739 				this.setStart( startContainer, startOffset );
740
741 				if ( collapsed )
742 					this.collapse( true );
743 			}
744
745 			var endContainer = this.endContainer;
746 			var endOffset = this.endOffset;
747
748 			if ( !( ignoreEnd || collapsed )
749 				 && endContainer && endContainer.type == CKEDITOR.NODE_TEXT )
750 			{
751 				// If the offset is zero, we just insert the new node before
752 				// the start.
753 				if ( !endOffset )
754 				{
755 					endOffset = endContainer.getIndex();
756 					endContainer = endContainer.getParent();
757 				}
758 				// If the offset is at the end, we'll insert it after the text
759 				// node.
760 				else if ( endOffset >= endContainer.getLength() )
761 				{
762 					endOffset = endContainer.getIndex() + 1;
763 					endContainer = endContainer.getParent();
764 				}
765 				// In other case, we split the text node and insert the new
766 				// node at the split point.
767 				else
768 				{
769 					endContainer.split( endOffset );
770
771 					endOffset = endContainer.getIndex() + 1;
772 					endContainer = endContainer.getParent();
773 				}
774
775 				this.setEnd( endContainer, endOffset );
776 			}
777 		},
778
779 		enlarge : function( unit )
780 		{
781 			switch ( unit )
782 			{
783 				case CKEDITOR.ENLARGE_ELEMENT :
784
785 					if ( this.collapsed )
786 						return;
787
788 					// Get the common ancestor.
789 					var commonAncestor = this.getCommonAncestor();
790
791 					var body = this.document.getBody();
792
793 					// For each boundary
794 					//		a. Depending on its position, find out the first node to be checked (a sibling) or, if not available, to be enlarge.
795 					//		b. Go ahead checking siblings and enlarging the boundary as much as possible until the common ancestor is not reached. After reaching the common ancestor, just save the enlargeable node to be used later.
796
797 					var startTop, endTop;
798
799 					var enlargeable, sibling, commonReached;
800
801 					// Indicates that the node can be added only if whitespace
802 					// is available before it.
803 					var needsWhiteSpace = false;
804 					var isWhiteSpace;
805 					var siblingText;
806
807 					// Process the start boundary.
808
809 					var container = this.startContainer;
810 					var offset = this.startOffset;
811
812 					if ( container.type == CKEDITOR.NODE_TEXT )
813 					{
814 						if ( offset )
815 						{
816 							// Check if there is any non-space text before the
817 							// offset. Otherwise, container is null.
818 							container = !CKEDITOR.tools.trim( container.substring( 0, offset ) ).length && container;
819
820 							// If we found only whitespace in the node, it
821 							// means that we'll need more whitespace to be able
822 							// to expand. For example, <i> can be expanded in
823 							// "A <i> [B]</i>", but not in "A<i> [B]</i>".
824 							needsWhiteSpace = !!container;
825 						}
826
827 						if ( container )
828 						{
829 							if ( !( sibling = container.getPrevious() ) )
830 								enlargeable = container.getParent();
831 						}
832 					}
833 					else
834 					{
835 						// If we have offset, get the node preceeding it as the
836 						// first sibling to be checked.
837 						if ( offset )
838 							sibling = container.getChild( offset - 1 ) || container.getLast();
839
840 						// If there is no sibling, mark the container to be
841 						// enlarged.
842 						if ( !sibling )
843 							enlargeable = container;
844 					}
845
846 					while ( enlargeable || sibling )
847 					{
848 						if ( enlargeable && !sibling )
849 						{
850 							// If we reached the common ancestor, mark the flag
851 							// for it.
852 							if ( !commonReached && enlargeable.equals( commonAncestor ) )
853 								commonReached = true;
854
855 							if ( !body.contains( enlargeable ) )
856 								break;
857
858 							// If we don't need space or this element breaks
859 							// the line, then enlarge it.
860 							if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
861 							{
862 								needsWhiteSpace = false;
863
864 								// If the common ancestor has been reached,
865 								// we'll not enlarge it immediately, but just
866 								// mark it to be enlarged later if the end
867 								// boundary also enlarges it.
868 								if ( commonReached )
869 									startTop = enlargeable;
870 								else
871 									this.setStartBefore( enlargeable );
872 							}
873
874 							sibling = enlargeable.getPrevious();
875 						}
876
877 						// Check all sibling nodes preceeding the enlargeable
878 						// node. The node wil lbe enlarged only if none of them
879 						// blocks it.
880 						while ( sibling )
881 						{
882 							// This flag indicates that this node has
883 							// whitespaces at the end.
884 							isWhiteSpace = false;
885
886 							if ( sibling.type == CKEDITOR.NODE_TEXT )
887 							{
888 								siblingText = sibling.getText();
889
890 								if ( /[^\s\ufeff]/.test( siblingText ) )
891 									sibling = null;
892
893 								isWhiteSpace = /[\s\ufeff]$/.test( siblingText );
894 							}
895 							else
896 							{
897 								// If this is a visible element.
898 								// We need to check for the bookmark attribute because IE insists on
899 								// rendering the display:none nodes we use for bookmarks. (#3363)
900 								if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) )
901 								{
902 									// We'll accept it only if we need
903 									// whitespace, and this is an inline
904 									// element with whitespace only.
905 									if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
906 									{
907 										// It must contains spaces and inline elements only.
908
909 										siblingText = sibling.getText();
910
911 										if ( !(/[^\s\ufeff]/).test( siblingText ) )	// Spaces + Zero Width No-Break Space (U+FEFF)
912 											sibling = null;
913 										else
914 										{
915 											var allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );
916 											for ( var i = 0, child ; child = allChildren[ i++ ] ; )
917 											{
918 												if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
919 												{
920 													sibling = null;
921 													break;
922 												}
923 											}
924 										}
925
926 										if ( sibling )
927 											isWhiteSpace = !!siblingText.length;
928 									}
929 									else
930 										sibling = null;
931 								}
932 							}
933
934 							// A node with whitespaces has been found.
935 							if ( isWhiteSpace )
936 							{
937 								// Enlarge the last enlargeable node, if we
938 								// were waiting for spaces.
939 								if ( needsWhiteSpace )
940 								{
941 									if ( commonReached )
942 										startTop = enlargeable;
943 									else if ( enlargeable )
944 										this.setStartBefore( enlargeable );
945 								}
946 								else
947 									needsWhiteSpace = true;
948 							}
949
950 							if ( sibling )
951 							{
952 								var next = sibling.getPrevious();
953
954 								if ( !enlargeable && !next )
955 								{
956 									// Set the sibling as enlargeable, so it's
957 									// parent will be get later outside this while.
958 									enlargeable = sibling;
959 									sibling = null;
960 									break;
961 								}
962
963 								sibling = next;
964 							}
965 							else
966 							{
967 								// If sibling has been set to null, then we
968 								// need to stop enlarging.
969 								enlargeable = null;
970 							}
971 						}
972
973 						if ( enlargeable )
974 							enlargeable = enlargeable.getParent();
975 					}
976
977 					// Process the end boundary. This is basically the same
978 					// code used for the start boundary, with small changes to
979 					// make it work in the oposite side (to the right). This
980 					// makes it difficult to reuse the code here. So, fixes to
981 					// the above code are likely to be replicated here.
982
983 					container = this.endContainer;
984 					offset = this.endOffset;
985
986 					// Reset the common variables.
987 					enlargeable = sibling = null;
988 					commonReached = needsWhiteSpace = false;
989
990 					if ( container.type == CKEDITOR.NODE_TEXT )
991 					{
992 						// Check if there is any non-space text after the
993 						// offset. Otherwise, container is null.
994 						container = !CKEDITOR.tools.trim( container.substring( offset ) ).length && container;
995
996 						// If we found only whitespace in the node, it
997 						// means that we'll need more whitespace to be able
998 						// to expand. For example, <i> can be expanded in
999 						// "A <i> [B]</i>", but not in "A<i> [B]</i>".
1000 						needsWhiteSpace = !( container && container.getLength() );
1001
1002 						if ( container )
1003 						{
1004 							if ( !( sibling = container.getNext() ) )
1005 								enlargeable = container.getParent();
1006 						}
1007 					}
1008 					else
1009 					{
1010 						// Get the node right after the boudary to be checked
1011 						// first.
1012 						sibling = container.getChild( offset );
1013
1014 						if ( !sibling )
1015 							enlargeable = container;
1016 					}
1017
1018 					while ( enlargeable || sibling )
1019 					{
1020 						if ( enlargeable && !sibling )
1021 						{
1022 							if ( !commonReached && enlargeable.equals( commonAncestor ) )
1023 								commonReached = true;
1024
1025 							if ( !body.contains( enlargeable ) )
1026 								break;
1027
1028 							if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
1029 							{
1030 								needsWhiteSpace = false;
1031
1032 								if ( commonReached )
1033 									endTop = enlargeable;
1034 								else if ( enlargeable )
1035 									this.setEndAfter( enlargeable );
1036 							}
1037
1038 							sibling = enlargeable.getNext();
1039 						}
1040
1041 						while ( sibling )
1042 						{
1043 							isWhiteSpace = false;
1044
1045 							if ( sibling.type == CKEDITOR.NODE_TEXT )
1046 							{
1047 								siblingText = sibling.getText();
1048
1049 								if ( /[^\s\ufeff]/.test( siblingText ) )
1050 									sibling = null;
1051
1052 								isWhiteSpace = /^[\s\ufeff]/.test( siblingText );
1053 							}
1054 							else
1055 							{
1056 								// If this is a visible element.
1057 								// We need to check for the bookmark attribute because IE insists on
1058 								// rendering the display:none nodes we use for bookmarks. (#3363)
1059 								if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) )
1060 								{
1061 									// We'll accept it only if we need
1062 									// whitespace, and this is an inline
1063 									// element with whitespace only.
1064 									if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
1065 									{
1066 										// It must contains spaces and inline elements only.
1067
1068 										siblingText = sibling.getText();
1069
1070 										if ( !(/[^\s\ufeff]/).test( siblingText ) )
1071 											sibling = null;
1072 										else
1073 										{
1074 											allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );
1075 											for ( i = 0 ; child = allChildren[ i++ ] ; )
1076 											{
1077 												if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
1078 												{
1079 													sibling = null;
1080 													break;
1081 												}
1082 											}
1083 										}
1084
1085 										if ( sibling )
1086 											isWhiteSpace = !!siblingText.length;
1087 									}
1088 									else
1089 										sibling = null;
1090 								}
1091 							}
1092
1093 							if ( isWhiteSpace )
1094 							{
1095 								if ( needsWhiteSpace )
1096 								{
1097 									if ( commonReached )
1098 										endTop = enlargeable;
1099 									else
1100 										this.setEndAfter( enlargeable );
1101 								}
1102 							}
1103
1104 							if ( sibling )
1105 							{
1106 								next = sibling.getNext();
1107
1108 								if ( !enlargeable && !next )
1109 								{
1110 									enlargeable = sibling;
1111 									sibling = null;
1112 									break;
1113 								}
1114
1115 								sibling = next;
1116 							}
1117 							else
1118 							{
1119 								// If sibling has been set to null, then we
1120 								// need to stop enlarging.
1121 								enlargeable = null;
1122 							}
1123 						}
1124
1125 						if ( enlargeable )
1126 							enlargeable = enlargeable.getParent();
1127 					}
1128
1129 					// If the common ancestor can be enlarged by both boundaries, then include it also.
1130 					if ( startTop && endTop )
1131 					{
1132 						commonAncestor = startTop.contains( endTop ) ? endTop : startTop;
1133
1134 						this.setStartBefore( commonAncestor );
1135 						this.setEndAfter( commonAncestor );
1136 					}
1137 					break;
1138
1139 				case CKEDITOR.ENLARGE_BLOCK_CONTENTS:
1140 				case CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS:
1141
1142 					// Enlarging the start boundary.
1143 					var walkerRange = new CKEDITOR.dom.range( this.document );
1144
1145 					body = this.document.getBody();
1146
1147 					walkerRange.setStartAt( body, CKEDITOR.POSITION_AFTER_START );
1148 					walkerRange.setEnd( this.startContainer, this.startOffset );
1149
1150 					var walker = new CKEDITOR.dom.walker( walkerRange ),
1151 					    blockBoundary,  // The node on which the enlarging should stop.
1152 						tailBr, //
1153 					    defaultGuard = CKEDITOR.dom.walker.blockBoundary(
1154 								( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ? { br : 1 } : null ),
1155 						// Record the encountered 'blockBoundary' for later use.
1156 						boundaryGuard = function( node )
1157 						{
1158 							var retval = defaultGuard( node );
1159 							if ( !retval )
1160 								blockBoundary = node;
1161 							return retval;
1162 						},
1163 						// Record the encounted 'tailBr' for later use.
1164 						tailBrGuard = function( node )
1165 						{
1166 							var retval = boundaryGuard( node );
1167 							if ( !retval && node.is && node.is( 'br' ) )
1168 								tailBr = node;
1169 							return retval;
1170 						};
1171
1172 					walker.guard = boundaryGuard;
1173
1174
1175 					if ( ( enlargeable = walker.lastBackward() ) )
1176 					{
1177 						// It's the body which stop the enlaring if no block boundary found.
1178 						blockBoundary = blockBoundary || body;
1179
1180 						// Start the range at different position by comparing
1181 						// the document position of it with 'enlargeable' node.
1182 						this.setStartAt(
1183 								blockBoundary,
1184 								blockBoundary.contains( enlargeable ) ?
1185 									CKEDITOR.POSITION_AFTER_START :
1186 									CKEDITOR.POSITION_AFTER_END );
1187 					}
1188
1189 					// Enlarging the end boundary.
1190 					walkerRange = this.clone();
1191 					walkerRange.collapse();
1192 					walkerRange.setEndAt( body, CKEDITOR.POSITION_BEFORE_END );
1193 					walker = new CKEDITOR.dom.walker( walkerRange );
1194
1195 					// tailBrGuard only used for on range end.
1196 					walker.guard = ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ?
1197 						tailBrGuard : boundaryGuard;
1198 					blockBoundary = null;
1199 					// End the range right before the block boundary node.
1200
1201 					if ( ( enlargeable = walker.lastForward() ) )
1202 					{
1203 						// It's the body which stop the enlaring if no block boundary found.
1204 						blockBoundary = blockBoundary || body;
1205
1206 						// Start the range at different position by comparing
1207 						// the document position of it with 'enlargeable' node.
1208 						this.setEndAt(
1209 								blockBoundary,
1210 								blockBoundary.contains( enlargeable ) ?
1211 									CKEDITOR.POSITION_BEFORE_END :
1212 									CKEDITOR.POSITION_BEFORE_START );
1213 					}
1214 					// We must include the <br> at the end of range if there's
1215 					// one and we're expanding list item contents
1216 					if ( tailBr )
1217 						this.setEndAfter( tailBr );
1218 			}
1219 		},
1220
1221 		/**
1222 		 * Inserts a node at the start of the range. The range will be expanded
1223 		 * the contain the node.
1224 		 */
1225 		insertNode : function( node )
1226 		{
1227 			this.optimizeBookmark();
1228 			this.trim( false, true );
1229
1230 			var startContainer = this.startContainer;
1231 			var startOffset = this.startOffset;
1232
1233 			var nextNode = startContainer.getChild( startOffset );
1234
1235 			if ( nextNode )
1236 				node.insertBefore( nextNode );
1237 			else
1238 				startContainer.append( node );
1239
1240 			// Check if we need to update the end boundary.
1241 			if ( node.getParent().equals( this.endContainer ) )
1242 				this.endOffset++;
1243
1244 			// Expand the range to embrace the new node.
1245 			this.setStartBefore( node );
1246 		},
1247
1248 		moveToPosition : function( node, position )
1249 		{
1250 			this.setStartAt( node, position );
1251 			this.collapse( true );
1252 		},
1253
1254 		selectNodeContents : function( node )
1255 		{
1256 			this.setStart( node, 0 );
1257 			this.setEnd( node, node.type == CKEDITOR.NODE_TEXT ? node.getLength() : node.getChildCount() );
1258 		},
1259
1260 		/**
1261 		 * Sets the start position of a Range.
1262 		 * @param {CKEDITOR.dom.node} startNode The node to start the range.
1263 		 * @param {Number} startOffset An integer greater than or equal to zero
1264 		 *		representing the offset for the start of the range from the start
1265 		 *		of startNode.
1266 		 */
1267 		setStart : function( startNode, startOffset )
1268 		{
1269 			// W3C requires a check for the new position. If it is after the end
1270 			// boundary, the range should be collapsed to the new start. It seams
1271 			// we will not need this check for our use of this class so we can
1272 			// ignore it for now.
1273
1274 			this.startContainer	= startNode;
1275 			this.startOffset	= startOffset;
1276
1277 			if ( !this.endContainer )
1278 			{
1279 				this.endContainer	= startNode;
1280 				this.endOffset		= startOffset;
1281 			}
1282
1283 			updateCollapsed( this );
1284 		},
1285
1286 		/**
1287 		 * Sets the end position of a Range.
1288 		 * @param {CKEDITOR.dom.node} endNode The node to end the range.
1289 		 * @param {Number} endOffset An integer greater than or equal to zero
1290 		 *		representing the offset for the end of the range from the start
1291 		 *		of endNode.
1292 		 */
1293 		setEnd : function( endNode, endOffset )
1294 		{
1295 			// W3C requires a check for the new position. If it is before the start
1296 			// boundary, the range should be collapsed to the new end. It seams we
1297 			// will not need this check for our use of this class so we can ignore
1298 			// it for now.
1299
1300 			this.endContainer	= endNode;
1301 			this.endOffset		= endOffset;
1302
1303 			if ( !this.startContainer )
1304 			{
1305 				this.startContainer	= endNode;
1306 				this.startOffset	= endOffset;
1307 			}
1308
1309 			updateCollapsed( this );
1310 		},
1311
1312 		setStartAfter : function( node )
1313 		{
1314 			this.setStart( node.getParent(), node.getIndex() + 1 );
1315 		},
1316
1317 		setStartBefore : function( node )
1318 		{
1319 			this.setStart( node.getParent(), node.getIndex() );
1320 		},
1321
1322 		setEndAfter : function( node )
1323 		{
1324 			this.setEnd( node.getParent(), node.getIndex() + 1 );
1325 		},
1326
1327 		setEndBefore : function( node )
1328 		{
1329 			this.setEnd( node.getParent(), node.getIndex() );
1330 		},
1331
1332 		setStartAt : function( node, position )
1333 		{
1334 			switch( position )
1335 			{
1336 				case CKEDITOR.POSITION_AFTER_START :
1337 					this.setStart( node, 0 );
1338 					break;
1339
1340 				case CKEDITOR.POSITION_BEFORE_END :
1341 					if ( node.type == CKEDITOR.NODE_TEXT )
1342 						this.setStart( node, node.getLength() );
1343 					else
1344 						this.setStart( node, node.getChildCount() );
1345 					break;
1346
1347 				case CKEDITOR.POSITION_BEFORE_START :
1348 					this.setStartBefore( node );
1349 					break;
1350
1351 				case CKEDITOR.POSITION_AFTER_END :
1352 					this.setStartAfter( node );
1353 			}
1354
1355 			updateCollapsed( this );
1356 		},
1357
1358 		setEndAt : function( node, position )
1359 		{
1360 			switch( position )
1361 			{
1362 				case CKEDITOR.POSITION_AFTER_START :
1363 					this.setEnd( node, 0 );
1364 					break;
1365
1366 				case CKEDITOR.POSITION_BEFORE_END :
1367 					if ( node.type == CKEDITOR.NODE_TEXT )
1368 						this.setEnd( node, node.getLength() );
1369 					else
1370 						this.setEnd( node, node.getChildCount() );
1371 					break;
1372
1373 				case CKEDITOR.POSITION_BEFORE_START :
1374 					this.setEndBefore( node );
1375 					break;
1376
1377 				case CKEDITOR.POSITION_AFTER_END :
1378 					this.setEndAfter( node );
1379 			}
1380
1381 			updateCollapsed( this );
1382 		},
1383
1384 		fixBlock : function( isStart, blockTag )
1385 		{
1386 			var bookmark = this.createBookmark(),
1387 				fixedBlock = this.document.createElement( blockTag );
1388
1389 			this.collapse( isStart );
1390
1391 			this.enlarge( CKEDITOR.ENLARGE_BLOCK_CONTENTS );
1392
1393 			this.extractContents().appendTo( fixedBlock );
1394 			fixedBlock.trim();
1395
1396 			if ( !CKEDITOR.env.ie )
1397 				fixedBlock.appendBogus();
1398
1399 			this.insertNode( fixedBlock );
1400
1401 			this.moveToBookmark( bookmark );
1402
1403 			return fixedBlock;
1404 		},
1405
1406 		splitBlock : function( blockTag )
1407 		{
1408 			var startPath	= new CKEDITOR.dom.elementPath( this.startContainer ),
1409 				endPath		= new CKEDITOR.dom.elementPath( this.endContainer );
1410
1411 			var startBlockLimit	= startPath.blockLimit,
1412 				endBlockLimit	= endPath.blockLimit;
1413
1414 			var startBlock	= startPath.block,
1415 				endBlock	= endPath.block;
1416
1417 			var elementPath = null;
1418 			// Do nothing if the boundaries are in different block limits.
1419 			if ( !startBlockLimit.equals( endBlockLimit ) )
1420 				return null;
1421
1422 			// Get or fix current blocks.
1423 			if ( blockTag != 'br' )
1424 			{
1425 				if ( !startBlock )
1426 				{
1427 					startBlock = this.fixBlock( true, blockTag );
1428 					endBlock = new CKEDITOR.dom.elementPath( this.endContainer ).block;
1429 				}
1430
1431 				if ( !endBlock )
1432 					endBlock = this.fixBlock( false, blockTag );
1433 			}
1434
1435 			// Get the range position.
1436 			var isStartOfBlock = startBlock && this.checkStartOfBlock(),
1437 				isEndOfBlock = endBlock && this.checkEndOfBlock();
1438
1439 			// Delete the current contents.
1440 			// TODO: Why is 2.x doing CheckIsEmpty()?
1441 			this.deleteContents();
1442
1443 			if ( startBlock && startBlock.equals( endBlock ) )
1444 			{
1445 				if ( isEndOfBlock )
1446 				{
1447 					elementPath = new CKEDITOR.dom.elementPath( this.startContainer );
1448 					this.moveToPosition( endBlock, CKEDITOR.POSITION_AFTER_END );
1449 					endBlock = null;
1450 				}
1451 				else if ( isStartOfBlock )
1452 				{
1453 					elementPath = new CKEDITOR.dom.elementPath( this.startContainer );
1454 					this.moveToPosition( startBlock, CKEDITOR.POSITION_BEFORE_START );
1455 					startBlock = null;
1456 				}
1457 				else
1458 				{
1459 					// Extract the contents of the block from the selection point to the end
1460 					// of its contents.
1461 					this.setEndAt( startBlock, CKEDITOR.POSITION_BEFORE_END );
1462 					var documentFragment = this.extractContents();
1463
1464 					// Duplicate the block element after it.
1465 					endBlock = startBlock.clone( false );
1466
1467 					// Place the extracted contents into the duplicated block.
1468 					documentFragment.appendTo( endBlock );
1469 					endBlock.insertAfter( startBlock );
1470 					this.moveToPosition( startBlock, CKEDITOR.POSITION_AFTER_END );
1471
1472 					// In Gecko, the last child node must be a bogus <br>.
1473 					// Note: bogus <br> added under <ul> or <ol> would cause
1474 					// lists to be incorrectly rendered.
1475 					if ( !CKEDITOR.env.ie && !startBlock.is( 'ul', 'ol') )
1476 						startBlock.appendBogus() ;
1477 				}
1478 			}
1479
1480 			return {
1481 				previousBlock : startBlock,
1482 				nextBlock : endBlock,
1483 				wasStartOfBlock : isStartOfBlock,
1484 				wasEndOfBlock : isEndOfBlock,
1485 				elementPath : elementPath
1486 			};
1487 		},
1488
1489 		/**
1490 		 * Check whether current range is on the inner edge of the specified element.
1491 		 * @param {Number} checkType ( CKEDITOR.START | CKEDITOR.END ) The checking side.
1492 		 * @param {CKEDITOR.dom.element} element The target element to check.
1493 		 */
1494 		checkBoundaryOfElement : function( element, checkType )
1495 		{
1496 			var walkerRange = this.clone();
1497 			// Expand the range to element boundary.
1498 			walkerRange[ checkType == CKEDITOR.START ?
1499 			 'setStartAt' : 'setEndAt' ]
1500 			 ( element, checkType == CKEDITOR.START ?
1501 			   CKEDITOR.POSITION_AFTER_START
1502 			   : CKEDITOR.POSITION_BEFORE_END );
1503
1504 			var walker = new CKEDITOR.dom.walker( walkerRange ),
1505 			 retval = false;
1506 			walker.evaluator = elementBoundaryEval;
1507 			return walker[ checkType == CKEDITOR.START ?
1508 				'checkBackward' : 'checkForward' ]();
1509 		},
1510 		// Calls to this function may produce changes to the DOM. The range may
1511 		// be updated to reflect such changes.
1512 		checkStartOfBlock : function()
1513 		{
1514 			var startContainer = this.startContainer,
1515 				startOffset = this.startOffset;
1516
1517 			// If the starting node is a text node, and non-empty before the offset,
1518 			// then we're surely not at the start of block.
1519 			if ( startOffset && startContainer.type == CKEDITOR.NODE_TEXT )
1520 			{
1521 				var textBefore = CKEDITOR.tools.ltrim( startContainer.substring( 0, startOffset ) );
1522 				if ( textBefore.length )
1523 					return false;
1524 			}
1525
1526 			// Antecipate the trim() call here, so the walker will not make
1527 			// changes to the DOM, which would not get reflected into this
1528 			// range otherwise.
1529 			this.trim();
1530
1531 			// We need to grab the block element holding the start boundary, so
1532 			// let's use an element path for it.
1533 			var path = new CKEDITOR.dom.elementPath( this.startContainer );
1534
1535 			// Creates a range starting at the block start until the range start.
1536 			var walkerRange = this.clone();
1537 			walkerRange.collapse( true );
1538 			walkerRange.setStartAt( path.block || path.blockLimit, CKEDITOR.POSITION_AFTER_START );
1539
1540 			var walker = new CKEDITOR.dom.walker( walkerRange );
1541 			walker.evaluator = getCheckStartEndBlockEvalFunction( true );
1542
1543 			return walker.checkBackward();
1544 		},
1545
1546 		checkEndOfBlock : function()
1547 		{
1548 			var endContainer = this.endContainer,
1549 				endOffset = this.endOffset;
1550
1551 			// If the ending node is a text node, and non-empty after the offset,
1552 			// then we're surely not at the end of block.
1553 			if ( endContainer.type == CKEDITOR.NODE_TEXT )
1554 			{
1555 				var textAfter = CKEDITOR.tools.rtrim( endContainer.substring( endOffset ) );
1556 				if ( textAfter.length )
1557 					return false;
1558 			}
1559
1560 			// Antecipate the trim() call here, so the walker will not make
1561 			// changes to the DOM, which would not get reflected into this
1562 			// range otherwise.
1563 			this.trim();
1564
1565 			// We need to grab the block element holding the start boundary, so
1566 			// let's use an element path for it.
1567 			var path = new CKEDITOR.dom.elementPath( this.endContainer );
1568
1569 			// Creates a range starting at the block start until the range start.
1570 			var walkerRange = this.clone();
1571 			walkerRange.collapse( false );
1572 			walkerRange.setEndAt( path.block || path.blockLimit, CKEDITOR.POSITION_BEFORE_END );
1573
1574 			var walker = new CKEDITOR.dom.walker( walkerRange );
1575 			walker.evaluator = getCheckStartEndBlockEvalFunction( false );
1576
1577 			return walker.checkForward();
1578 		},
1579
1580 		/**
1581 		 * Moves the range boundaries to the first editing point inside an
1582 		 * element. For example, in an element tree like
1583 		 * "<p><b><i></i></b> Text</p>", the start editing point is
1584 		 * "<p><b><i>^</i></b> Text</p>" (inside <i>).
1585 		 * @param {CKEDITOR.dom.element} targetElement The element into which
1586 		 *		look for the editing spot.
1587 		 */
1588 		moveToElementEditStart : function( targetElement )
1589 		{
1590 			var editableElement;
1591
1592 			while ( targetElement && targetElement.type == CKEDITOR.NODE_ELEMENT )
1593 			{
1594 				if ( targetElement.isEditable() )
1595 					editableElement = targetElement;
1596 				else if ( editableElement )
1597 					break ;		// If we already found an editable element, stop the loop.
1598
1599 				targetElement = targetElement.getFirst();
1600 			}
1601
1602 			if ( editableElement )
1603 				this.moveToPosition( editableElement, CKEDITOR.POSITION_AFTER_START );
1604 		},
1605
1606 		getTouchedStartNode : function()
1607 		{
1608 			var container = this.startContainer ;
1609
1610 			if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT )
1611 				return container ;
1612
1613 			return container.getChild( this.startOffset ) || container ;
1614 		},
1615
1616 		getTouchedEndNode : function()
1617 		{
1618 			var container = this.endContainer ;
1619
1620 			if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT )
1621 				return container ;
1622
1623 			return container.getChild( this.endOffset - 1 ) || container ;
1624 		}
1625 	};
1626 })();
1627
1628 CKEDITOR.POSITION_AFTER_START	= 1;	// <element>^contents</element>		"^text"
1629 CKEDITOR.POSITION_BEFORE_END	= 2;	// <element>contents^</element>		"text^"
1630 CKEDITOR.POSITION_BEFORE_START	= 3;	// ^<element>contents</element>		^"text"
1631 CKEDITOR.POSITION_AFTER_END		= 4;	// <element>contents</element>^		"text"
1632
1633 CKEDITOR.ENLARGE_ELEMENT = 1;
1634 CKEDITOR.ENLARGE_BLOCK_CONTENTS = 2;
1635 CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3;
1636
1637 /**
1638  * Check boundary types.
1639  * @see CKEDITOR.dom.range::checkBoundaryOfElement
1640  */
1641 CKEDITOR.START = 1;
1642 CKEDITOR.END = 2;
1643 CKEDITOR.STARTEND = 3;
1644