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