1 /*
  2 Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved.
  3 For licensing, see LICENSE.html or http://ckeditor.com/license
  4 */
  5 
  6 /**
  7  * Creates a CKEDITOR.dom.range instance that can be used inside a specific
  8  * DOM Document.
  9  * @class Represents a delimited piece of content in a DOM Document.
 10  * It is contiguous in the sense that it can be characterized as selecting all
 11  * of the content between a pair of boundary-points.<br>
 12  * <br>
 13  * This class shares much of the W3C
 14  * <a href="http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html">Document Object Model Range</a>
 15  * ideas and features, adding several range manipulation tools to it, but it's
 16  * not intended to be compatible with it.
 17  * @param {CKEDITOR.dom.document} document The document into which the range
 18  *		features will be available.
 19  * @example
 20  * // Create a range for the entire contents of the editor document body.
 21  * var range = new CKEDITOR.dom.range( editor.document );
 22  * range.selectNodeContents( editor.document.getBody() );
 23  * // Delete the contents.
 24  * range.deleteContents();
 25  */
 26 CKEDITOR.dom.range = function( document )
 27 {
 28 	/**
 29 	 * Node within which the range begins.
 30 	 * @type {CKEDITOR.NODE_ELEMENT|CKEDITOR.NODE_TEXT}
 31 	 * @example
 32 	 * var range = new CKEDITOR.dom.range( editor.document );
 33 	 * range.selectNodeContents( editor.document.getBody() );
 34 	 * alert( range.startContainer.getName() );  // "body"
 35 	 */
 36 	this.startContainer	= null;
 37 
 38 	/**
 39 	 * Offset within the starting node of the range.
 40 	 * @type {Number}
 41 	 * @example
 42 	 * var range = new CKEDITOR.dom.range( editor.document );
 43 	 * range.selectNodeContents( editor.document.getBody() );
 44 	 * alert( range.startOffset );  // "0"
 45 	 */
 46 	this.startOffset	= null;
 47 
 48 	/**
 49 	 * Node within which the range ends.
 50 	 * @type {CKEDITOR.NODE_ELEMENT|CKEDITOR.NODE_TEXT}
 51 	 * @example
 52 	 * var range = new CKEDITOR.dom.range( editor.document );
 53 	 * range.selectNodeContents( editor.document.getBody() );
 54 	 * alert( range.endContainer.getName() );  // "body"
 55 	 */
 56 	this.endContainer	= null;
 57 
 58 	/**
 59 	 * Offset within the ending node of the range.
 60 	 * @type {Number}
 61 	 * @example
 62 	 * var range = new CKEDITOR.dom.range( editor.document );
 63 	 * range.selectNodeContents( editor.document.getBody() );
 64 	 * alert( range.endOffset );  // == editor.document.getBody().getChildCount()
 65 	 */
 66 	this.endOffset		= null;
 67 
 68 	/**
 69 	 * Indicates that this is a collapsed range. A collapsed range has it's
 70 	 * start and end boudaries at the very same point so nothing is contained
 71 	 * in it.
 72 	 * @example
 73 	 * var range = new CKEDITOR.dom.range( editor.document );
 74 	 * range.selectNodeContents( editor.document.getBody() );
 75 	 * alert( range.collapsed );  // "false"
 76 	 * range.collapse();
 77 	 * alert( range.collapsed );  // "true"
 78 	 */
 79 	this.collapsed		= true;
 80 
 81 	/**
 82 	 * The document within which the range can be used.
 83 	 * @type {CKEDITOR.dom.document}
 84 	 * @example
 85 	 * // Selects the body contents of the range document.
 86 	 * range.selectNodeContents( range.document.getBody() );
 87 	 */
 88 	this.document = document;
 89 };
 90 
 91 (function()
 92 {
 93 	// Updates the "collapsed" property for the given range object.
 94 	var updateCollapsed = function( range )
 95 	{
 96 		range.collapsed = (
 97 			range.startContainer &&
 98 			range.endContainer &&
 99 			range.startContainer.equals( range.endContainer ) &&
100 			range.startOffset == range.endOffset );
101 	};
102 
103 	// This is a shared function used to delete, extract and clone the range
104 	// contents.
105 	// V2
106 	var execContentsAction = function( range, action, docFrag, mergeThen )
107 	{
108 		range.optimizeBookmark();
109 
110 		var startNode	= range.startContainer;
111 		var endNode		= range.endContainer;
112 
113 		var startOffset	= range.startOffset;
114 		var endOffset	= range.endOffset;
115 
116 		var removeStartNode;
117 		var removeEndNode;
118 
119 		// For text containers, we must simply split the node and point to the
120 		// second part. The removal will be handled by the rest of the code .
121 		if ( endNode.type == CKEDITOR.NODE_TEXT )
122 			endNode = endNode.split( endOffset );
123 		else
124 		{
125 			// If the end container has children and the offset is pointing
126 			// to a child, then we should start from it.
127 			if ( endNode.getChildCount() > 0 )
128 			{
129 				// If the offset points after the last node.
130 				if ( endOffset >= endNode.getChildCount() )
131 				{
132 					// Let's create a temporary node and mark it for removal.
133 					endNode = endNode.append( range.document.createText( '' ) );
134 					removeEndNode = true;
135 				}
136 				else
137 					endNode = endNode.getChild( endOffset );
138 			}
139 		}
140 
141 		// For text containers, we must simply split the node. The removal will
142 		// be handled by the rest of the code .
143 		if ( startNode.type == CKEDITOR.NODE_TEXT )
144 		{
145 			startNode.split( startOffset );
146 
147 			// In cases the end node is the same as the start node, the above
148 			// splitting will also split the end, so me must move the end to
149 			// the second part of the split.
150 			if ( startNode.equals( endNode ) )
151 				endNode = startNode.getNext();
152 		}
153 		else
154 		{
155 			// If the start container has children and the offset is pointing
156 			// to a child, then we should start from its previous sibling.
157 
158 			// If the offset points to the first node, we don't have a
159 			// sibling, so let's use the first one, but mark it for removal.
160 			if ( !startOffset )
161 			{
162 				// Let's create a temporary node and mark it for removal.
163 				startNode = startNode.getFirst().insertBeforeMe( range.document.createText( '' ) );
164 				removeStartNode = true;
165 			}
166 			else if ( startOffset >= startNode.getChildCount() )
167 			{
168 				// Let's create a temporary node and mark it for removal.
169 				startNode = startNode.append( range.document.createText( '' ) );
170 				removeStartNode = true;
171 			}
172 			else
173 				startNode = startNode.getChild( startOffset ).getPrevious();
174 		}
175 
176 		// Get the parent nodes tree for the start and end boundaries.
177 		var startParents	= startNode.getParents();
178 		var endParents		= endNode.getParents();
179 
180 		// Compare them, to find the top most siblings.
181 		var i, topStart, topEnd;
182 
183 		for ( i = 0 ; i < startParents.length ; i++ )
184 		{
185 			topStart = startParents[ i ];
186 			topEnd = endParents[ i ];
187 
188 			// The compared nodes will match until we find the top most
189 			// siblings (different nodes that have the same parent).
190 			// "i" will hold the index in the parents array for the top
191 			// most element.
192 			if ( !topStart.equals( topEnd ) )
193 				break;
194 		}
195 
196 		var clone = docFrag, levelStartNode, levelClone, currentNode, currentSibling;
197 
198 		// Remove all successive sibling nodes for every node in the
199 		// startParents tree.
200 		for ( var j = i ; j < startParents.length ; j++ )
201 		{
202 			levelStartNode = startParents[j];
203 
204 			// For Extract and Clone, we must clone this level.
205 			if ( clone && !levelStartNode.equals( startNode ) )		// action = 0 = Delete
206 				levelClone = clone.append( levelStartNode.clone() );
207 
208 			currentNode = levelStartNode.getNext();
209 
210 			while ( currentNode )
211 			{
212 				// Stop processing when the current node matches a node in the
213 				// endParents tree or if it is the endNode.
214 				if ( currentNode.equals( endParents[ j ] ) || currentNode.equals( endNode ) )
215 					break;
216 
217 				// Cache the next sibling.
218 				currentSibling = currentNode.getNext();
219 
220 				// If cloning, just clone it.
221 				if ( action == 2 )	// 2 = Clone
222 					clone.append( currentNode.clone( true ) );
223 				else
224 				{
225 					// Both Delete and Extract will remove the node.
226 					currentNode.remove();
227 
228 					// When Extracting, move the removed node to the docFrag.
229 					if ( action == 1 )	// 1 = Extract
230 						clone.append( currentNode );
231 				}
232 
233 				currentNode = currentSibling;
234 			}
235 
236 			if ( clone )
237 				clone = levelClone;
238 		}
239 
240 		clone = docFrag;
241 
242 		// Remove all previous sibling nodes for every node in the
243 		// endParents tree.
244 		for ( var k = i ; k < endParents.length ; k++ )
245 		{
246 			levelStartNode = endParents[ k ];
247 
248 			// For Extract and Clone, we must clone this level.
249 			if ( action > 0 && !levelStartNode.equals( endNode ) )		// action = 0 = Delete
250 				levelClone = clone.append( levelStartNode.clone() );
251 
252 			// The processing of siblings may have already been done by the parent.
253 			if ( !startParents[ k ] || levelStartNode.$.parentNode != startParents[ k ].$.parentNode )
254 			{
255 				currentNode = levelStartNode.getPrevious();
256 
257 				while ( currentNode )
258 				{
259 					// Stop processing when the current node matches a node in the
260 					// startParents tree or if it is the startNode.
261 					if ( currentNode.equals( startParents[ k ] ) || currentNode.equals( startNode ) )
262 						break;
263 
264 					// Cache the next sibling.
265 					currentSibling = currentNode.getPrevious();
266 
267 					// If cloning, just clone it.
268 					if ( action == 2 )	// 2 = Clone
269 						clone.$.insertBefore( currentNode.$.cloneNode( true ), clone.$.firstChild ) ;
270 					else
271 					{
272 						// Both Delete and Extract will remove the node.
273 						currentNode.remove();
274 
275 						// When Extracting, mode the removed node to the docFrag.
276 						if ( action == 1 )	// 1 = Extract
277 							clone.$.insertBefore( currentNode.$, clone.$.firstChild );
278 					}
279 
280 					currentNode = currentSibling;
281 				}
282 			}
283 
284 			if ( clone )
285 				clone = levelClone;
286 		}
287 
288 		if ( action == 2 )		// 2 = Clone.
289 		{
290 			// No changes in the DOM should be done, so fix the split text (if any).
291 
292 			var startTextNode = range.startContainer;
293 			if ( startTextNode.type == CKEDITOR.NODE_TEXT )
294 			{
295 				startTextNode.$.data += startTextNode.$.nextSibling.data;
296 				startTextNode.$.parentNode.removeChild( startTextNode.$.nextSibling );
297 			}
298 
299 			var endTextNode = range.endContainer;
300 			if ( endTextNode.type == CKEDITOR.NODE_TEXT && endTextNode.$.nextSibling )
301 			{
302 				endTextNode.$.data += endTextNode.$.nextSibling.data;
303 				endTextNode.$.parentNode.removeChild( endTextNode.$.nextSibling );
304 			}
305 		}
306 		else
307 		{
308 			// Collapse the range.
309 
310 			// If a node has been partially selected, collapse the range between
311 			// topStart and topEnd. Otherwise, simply collapse it to the start. (W3C specs).
312 			if ( topStart && topEnd && ( startNode.$.parentNode != topStart.$.parentNode || endNode.$.parentNode != topEnd.$.parentNode ) )
313 			{
314 				var endIndex = topEnd.getIndex();
315 
316 				// If the start node is to be removed, we must correct the
317 				// index to reflect the removal.
318 				if ( removeStartNode && topEnd.$.parentNode == startNode.$.parentNode )
319 					endIndex--;
320 
321 				// Merge splitted parents.
322 				if ( mergeThen && topStart.type == CKEDITOR.NODE_ELEMENT )
323 				{
324 					var span = CKEDITOR.dom.element.createFromHtml( '<span ' +
325 						'data-cke-bookmark="1" style="display:none"> </span>', range.document );
326 					span.insertAfter( topStart );
327 					topStart.mergeSiblings( false );
328 					range.moveToBookmark( { startNode : span } );
329 				}
330 				else
331 					range.setStart( topEnd.getParent(), endIndex );
332 			}
333 
334 			// Collapse it to the start.
335 			range.collapse( true );
336 		}
337 
338 		// Cleanup any marked node.
339 		if ( removeStartNode )
340 			startNode.remove();
341 
342 		if ( removeEndNode && endNode.$.parentNode )
343 			endNode.remove();
344 	};
345 
346 	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 };
347 
348 	// Creates the appropriate node evaluator for the dom walker used inside
349 	// check(Start|End)OfBlock.
350 	function getCheckStartEndBlockEvalFunction()
351 	{
352 		var skipBogus = false,
353 			whitespaces = CKEDITOR.dom.walker.whitespaces(),
354 			bookmarkEvaluator = CKEDITOR.dom.walker.bookmark( true ),
355 			isBogus = CKEDITOR.dom.walker.bogus();
356 
357 		return function( node )
358 		{
359 			// First skip empty nodes.
360 			if ( bookmarkEvaluator( node ) || whitespaces( node ) )
361 				return true;
362 
363 			// Skip the bogus node at the end of block.
364 			if ( isBogus( node ) &&
365 				 !skipBogus )
366 			{
367 				skipBogus = true;
368 				return true;
369 			}
370 
371 			// If there's any visible text, then we're not at the start.
372 			if ( node.type == CKEDITOR.NODE_TEXT &&
373 					 ( node.hasAscendant( 'pre' ) ||
374 						 CKEDITOR.tools.trim( node.getText() ).length ) )
375 				return false;
376 
377 			// If there are non-empty inline elements (e.g. <img />), then we're not
378 			// at the start.
379 			if ( node.type == CKEDITOR.NODE_ELEMENT && !inlineChildReqElements[ node.getName() ] )
380 				return false;
381 
382 			return true;
383 		};
384 	}
385 
386 
387 	var isBogus = CKEDITOR.dom.walker.bogus();
388 	// Evaluator for CKEDITOR.dom.element::checkBoundaryOfElement, reject any
389 	// text node and non-empty elements unless it's being bookmark text.
390 	function elementBoundaryEval( checkStart )
391 	{
392 		var whitespaces = CKEDITOR.dom.walker.whitespaces(),
393 			bookmark = CKEDITOR.dom.walker.bookmark( 1 );
394 
395 		return function( node )
396 		{
397 			// First skip empty nodes.
398 			if ( bookmark( node ) || whitespaces( node ) )
399 				return true;
400 
401 			// Tolerant bogus br when checking at the end of block.
402 			// Reject any text node unless it's being bookmark
403 			// OR it's spaces.
404 			// Reject any element unless it's being invisible empty. (#3883)
405 			return !checkStart && isBogus( node ) ||
406 						 node.type == CKEDITOR.NODE_ELEMENT &&
407 						 node.getName() in CKEDITOR.dtd.$removeEmpty;
408 		};
409 	}
410 
411 	var whitespaceEval = new CKEDITOR.dom.walker.whitespaces(),
412 		bookmarkEval = new CKEDITOR.dom.walker.bookmark(),
413 		nbspRegExp = /^[\t\r\n ]*(?: |\xa0)$/;
414 
415 	function nonWhitespaceOrBookmarkEval( node )
416 	{
417 		// Whitespaces and bookmark nodes are to be ignored.
418 		return !whitespaceEval( node ) && !bookmarkEval( node );
419 	}
420 
421 	CKEDITOR.dom.range.prototype =
422 	{
423 		clone : function()
424 		{
425 			var clone = new CKEDITOR.dom.range( this.document );
426 
427 			clone.startContainer = this.startContainer;
428 			clone.startOffset = this.startOffset;
429 			clone.endContainer = this.endContainer;
430 			clone.endOffset = this.endOffset;
431 			clone.collapsed = this.collapsed;
432 
433 			return clone;
434 		},
435 
436 		collapse : function( toStart )
437 		{
438 			if ( toStart )
439 			{
440 				this.endContainer	= this.startContainer;
441 				this.endOffset		= this.startOffset;
442 			}
443 			else
444 			{
445 				this.startContainer	= this.endContainer;
446 				this.startOffset	= this.endOffset;
447 			}
448 
449 			this.collapsed = true;
450 		},
451 
452 		/**
453 		 *  The content nodes of the range are cloned and added to a document fragment, which is returned.
454 		 *  <strong> Note: </strong> Text selection may lost after invoking this method. (caused by text node splitting).
455 		 */
456 		cloneContents : function()
457 		{
458 			var docFrag = new CKEDITOR.dom.documentFragment( this.document );
459 
460 			if ( !this.collapsed )
461 				execContentsAction( this, 2, docFrag );
462 
463 			return docFrag;
464 		},
465 
466 		/**
467 		 * Deletes the content nodes of the range permanently from the DOM tree.
468 		 * @param {Boolean} [mergeThen] Merge any splitted elements result in DOM true due to partial selection.
469 		 */
470 		deleteContents : function( mergeThen )
471 		{
472 			if ( this.collapsed )
473 				return;
474 
475 			execContentsAction( this, 0, null, mergeThen );
476 		},
477 
478 		/**
479 		 *  The content nodes of the range are cloned and added to a document fragment,
480 		 * meanwhile they're removed permanently from the DOM tree.
481 		 * @param {Boolean} [mergeThen] Merge any splitted elements result in DOM true due to partial selection.
482 		 */
483 		extractContents : function( mergeThen )
484 		{
485 			var docFrag = new CKEDITOR.dom.documentFragment( this.document );
486 
487 			if ( !this.collapsed )
488 				execContentsAction( this, 1, docFrag, mergeThen );
489 
490 			return docFrag;
491 		},
492 
493 		/**
494 		 * Creates a bookmark object, which can be later used to restore the
495 		 * range by using the moveToBookmark function.
496 		 * This is an "intrusive" way to create a bookmark. It includes <span> tags
497 		 * in the range boundaries. The advantage of it is that it is possible to
498 		 * handle DOM mutations when moving back to the bookmark.
499 		 * Attention: the inclusion of nodes in the DOM is a design choice and
500 		 * should not be changed as there are other points in the code that may be
501 		 * using those nodes to perform operations. See GetBookmarkNode.
502 		 * @param {Boolean} [serializable] Indicates that the bookmark nodes
503 		 *		must contain ids, which can be used to restore the range even
504 		 *		when these nodes suffer mutations (like a clonation or innerHTML
505 		 *		change).
506 		 * @returns {Object} And object representing a bookmark.
507 		 */
508 		createBookmark : function( serializable )
509 		{
510 			var startNode, endNode;
511 			var baseId;
512 			var clone;
513 			var collapsed = this.collapsed;
514 
515 			startNode = this.document.createElement( 'span' );
516 			startNode.data( 'cke-bookmark', 1 );
517 			startNode.setStyle( 'display', 'none' );
518 
519 			// For IE, it must have something inside, otherwise it may be
520 			// removed during DOM operations.
521 			startNode.setHtml( ' ' );
522 
523 			if ( serializable )
524 			{
525 				baseId = 'cke_bm_' + CKEDITOR.tools.getNextNumber();
526 				startNode.setAttribute( 'id', baseId + ( collapsed ? 'C' : 'S' ) );
527 			}
528 
529 			// If collapsed, the endNode will not be created.
530 			if ( !collapsed )
531 			{
532 				endNode = startNode.clone();
533 				endNode.setHtml( ' ' );
534 
535 				if ( serializable )
536 					endNode.setAttribute( 'id', baseId + 'E' );
537 
538 				clone = this.clone();
539 				clone.collapse();
540 				clone.insertNode( endNode );
541 			}
542 
543 			clone = this.clone();
544 			clone.collapse( true );
545 			clone.insertNode( startNode );
546 
547 			// Update the range position.
548 			if ( endNode )
549 			{
550 				this.setStartAfter( startNode );
551 				this.setEndBefore( endNode );
552 			}
553 			else
554 				this.moveToPosition( startNode, CKEDITOR.POSITION_AFTER_END );
555 
556 			return {
557 				startNode : serializable ? baseId + ( collapsed ? 'C' : 'S' ) : startNode,
558 				endNode : serializable ? baseId + 'E' : endNode,
559 				serializable : serializable,
560 				collapsed : collapsed
561 			};
562 		},
563 
564 		/**
565 		 * Creates a "non intrusive" and "mutation sensible" bookmark. This
566 		 * kind of bookmark should be used only when the DOM is supposed to
567 		 * remain stable after its creation.
568 		 * @param {Boolean} [normalized] Indicates that the bookmark must
569 		 *		normalized. When normalized, the successive text nodes are
570 		 *		considered a single node. To sucessful load a normalized
571 		 *		bookmark, the DOM tree must be also normalized before calling
572 		 *		moveToBookmark.
573 		 * @returns {Object} An object representing the bookmark.
574 		 */
575 		createBookmark2 : function( normalized )
576 		{
577 			var startContainer	= this.startContainer,
578 				endContainer	= this.endContainer;
579 
580 			var startOffset	= this.startOffset,
581 				endOffset	= this.endOffset;
582 
583 			var collapsed = this.collapsed;
584 
585 			var child, previous;
586 
587 			// If there is no range then get out of here.
588 			// It happens on initial load in Safari #962 and if the editor it's
589 			// hidden also in Firefox
590 			if ( !startContainer || !endContainer )
591 				return { start : 0, end : 0 };
592 
593 			if ( normalized )
594 			{
595 				// Find out if the start is pointing to a text node that will
596 				// be normalized.
597 				if ( startContainer.type == CKEDITOR.NODE_ELEMENT )
598 				{
599 					child = startContainer.getChild( startOffset );
600 
601 					// In this case, move the start information to that text
602 					// node.
603 					if ( child && child.type == CKEDITOR.NODE_TEXT
604 							&& startOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT )
605 					{
606 						startContainer = child;
607 						startOffset = 0;
608 					}
609 
610 					// Get the normalized offset.
611 					if ( child && child.type == CKEDITOR.NODE_ELEMENT )
612 						startOffset = child.getIndex( 1 );
613 				}
614 
615 				// Normalize the start.
616 				while ( startContainer.type == CKEDITOR.NODE_TEXT
617 						&& ( previous = startContainer.getPrevious() )
618 						&& previous.type == CKEDITOR.NODE_TEXT )
619 				{
620 					startContainer = previous;
621 					startOffset += previous.getLength();
622 				}
623 
624 				// Process the end only if not normalized.
625 				if ( !collapsed )
626 				{
627 					// Find out if the start is pointing to a text node that
628 					// will be normalized.
629 					if ( endContainer.type == CKEDITOR.NODE_ELEMENT )
630 					{
631 						child = endContainer.getChild( endOffset );
632 
633 						// In this case, move the start information to that
634 						// text node.
635 						if ( child && child.type == CKEDITOR.NODE_TEXT
636 								&& endOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT )
637 						{
638 							endContainer = child;
639 							endOffset = 0;
640 						}
641 
642 						// Get the normalized offset.
643 						if ( child && child.type == CKEDITOR.NODE_ELEMENT )
644 							endOffset = child.getIndex( 1 );
645 					}
646 
647 					// Normalize the end.
648 					while ( endContainer.type == CKEDITOR.NODE_TEXT
649 							&& ( previous = endContainer.getPrevious() )
650 							&& previous.type == CKEDITOR.NODE_TEXT )
651 					{
652 						endContainer = previous;
653 						endOffset += previous.getLength();
654 					}
655 				}
656 			}
657 
658 			return {
659 				start		: startContainer.getAddress( normalized ),
660 				end			: collapsed ? null : endContainer.getAddress( normalized ),
661 				startOffset	: startOffset,
662 				endOffset	: endOffset,
663 				normalized	: normalized,
664 				collapsed	: collapsed,
665 				is2			: true		// It's a createBookmark2 bookmark.
666 			};
667 		},
668 
669 		moveToBookmark : function( bookmark )
670 		{
671 			if ( bookmark.is2 )		// Created with createBookmark2().
672 			{
673 				// Get the start information.
674 				var startContainer	= this.document.getByAddress( bookmark.start, bookmark.normalized ),
675 					startOffset	= bookmark.startOffset;
676 
677 				// Get the end information.
678 				var endContainer	= bookmark.end && this.document.getByAddress( bookmark.end, bookmark.normalized ),
679 					endOffset	= bookmark.endOffset;
680 
681 				// Set the start boundary.
682 				this.setStart( startContainer, startOffset );
683 
684 				// Set the end boundary. If not available, collapse it.
685 				if ( endContainer )
686 					this.setEnd( endContainer, endOffset );
687 				else
688 					this.collapse( true );
689 			}
690 			else					// Created with createBookmark().
691 			{
692 				var serializable = bookmark.serializable,
693 					startNode	= serializable ? this.document.getById( bookmark.startNode ) : bookmark.startNode,
694 					endNode		= serializable ? this.document.getById( bookmark.endNode ) : bookmark.endNode;
695 
696 				// Set the range start at the bookmark start node position.
697 				this.setStartBefore( startNode );
698 
699 				// Remove it, because it may interfere in the setEndBefore call.
700 				startNode.remove();
701 
702 				// Set the range end at the bookmark end node position, or simply
703 				// collapse it if it is not available.
704 				if ( endNode )
705 				{
706 					this.setEndBefore( endNode );
707 					endNode.remove();
708 				}
709 				else
710 					this.collapse( true );
711 			}
712 		},
713 
714 		getBoundaryNodes : function()
715 		{
716 			var startNode = this.startContainer,
717 				endNode = this.endContainer,
718 				startOffset = this.startOffset,
719 				endOffset = this.endOffset,
720 				childCount;
721 
722 			if ( startNode.type == CKEDITOR.NODE_ELEMENT )
723 			{
724 				childCount = startNode.getChildCount();
725 				if ( childCount > startOffset )
726 					startNode = startNode.getChild( startOffset );
727 				else if ( childCount < 1 )
728 					startNode = startNode.getPreviousSourceNode();
729 				else		// startOffset > childCount but childCount is not 0
730 				{
731 					// Try to take the node just after the current position.
732 					startNode = startNode.$;
733 					while ( startNode.lastChild )
734 						startNode = startNode.lastChild;
735 					startNode = new CKEDITOR.dom.node( startNode );
736 
737 					// Normally we should take the next node in DFS order. But it
738 					// is also possible that we've already reached the end of
739 					// document.
740 					startNode = startNode.getNextSourceNode() || startNode;
741 				}
742 			}
743 			if ( endNode.type == CKEDITOR.NODE_ELEMENT )
744 			{
745 				childCount = endNode.getChildCount();
746 				if ( childCount > endOffset )
747 					endNode = endNode.getChild( endOffset ).getPreviousSourceNode( true );
748 				else if ( childCount < 1 )
749 					endNode = endNode.getPreviousSourceNode();
750 				else		// endOffset > childCount but childCount is not 0
751 				{
752 					// Try to take the node just before the current position.
753 					endNode = endNode.$;
754 					while ( endNode.lastChild )
755 						endNode = endNode.lastChild;
756 					endNode = new CKEDITOR.dom.node( endNode );
757 				}
758 			}
759 
760 			// Sometimes the endNode will come right before startNode for collapsed
761 			// ranges. Fix it. (#3780)
762 			if ( startNode.getPosition( endNode ) & CKEDITOR.POSITION_FOLLOWING )
763 				startNode = endNode;
764 
765 			return { startNode : startNode, endNode : endNode };
766 		},
767 
768 		/**
769 		 * Find the node which fully contains the range.
770 		 * @param includeSelf
771 		 * @param {Boolean} ignoreTextNode Whether ignore CKEDITOR.NODE_TEXT type.
772 		 */
773 		getCommonAncestor : function( includeSelf , ignoreTextNode )
774 		{
775 			var start = this.startContainer,
776 				end = this.endContainer,
777 				ancestor;
778 
779 			if ( start.equals( end ) )
780 			{
781 				if ( includeSelf
782 						&& start.type == CKEDITOR.NODE_ELEMENT
783 						&& this.startOffset == this.endOffset - 1 )
784 					ancestor = start.getChild( this.startOffset );
785 				else
786 					ancestor = start;
787 			}
788 			else
789 				ancestor = start.getCommonAncestor( end );
790 
791 			return ignoreTextNode && !ancestor.is ? ancestor.getParent() : ancestor;
792 		},
793 
794 		/**
795 		 * Transforms the startContainer and endContainer properties from text
796 		 * nodes to element nodes, whenever possible. This is actually possible
797 		 * if either of the boundary containers point to a text node, and its
798 		 * offset is set to zero, or after the last char in the node.
799 		 */
800 		optimize : function()
801 		{
802 			var container = this.startContainer;
803 			var offset = this.startOffset;
804 
805 			if ( container.type != CKEDITOR.NODE_ELEMENT )
806 			{
807 				if ( !offset )
808 					this.setStartBefore( container );
809 				else if ( offset >= container.getLength() )
810 					this.setStartAfter( container );
811 			}
812 
813 			container = this.endContainer;
814 			offset = this.endOffset;
815 
816 			if ( container.type != CKEDITOR.NODE_ELEMENT )
817 			{
818 				if ( !offset )
819 					this.setEndBefore( container );
820 				else if ( offset >= container.getLength() )
821 					this.setEndAfter( container );
822 			}
823 		},
824 
825 		/**
826 		 * Move the range out of bookmark nodes if they'd been the container.
827 		 */
828 		optimizeBookmark: function()
829 		{
830 			var startNode = this.startContainer,
831 				endNode = this.endContainer;
832 
833 			if ( startNode.is && startNode.is( 'span' )
834 				&& startNode.data( 'cke-bookmark' ) )
835 				this.setStartAt( startNode, CKEDITOR.POSITION_BEFORE_START );
836 			if ( endNode && endNode.is && endNode.is( 'span' )
837 				&& endNode.data( 'cke-bookmark' ) )
838 				this.setEndAt( endNode,  CKEDITOR.POSITION_AFTER_END );
839 		},
840 
841 		trim : function( ignoreStart, ignoreEnd )
842 		{
843 			var startContainer = this.startContainer,
844 				startOffset = this.startOffset,
845 				collapsed = this.collapsed;
846 			if ( ( !ignoreStart || collapsed )
847 				 && startContainer && startContainer.type == CKEDITOR.NODE_TEXT )
848 			{
849 				// If the offset is zero, we just insert the new node before
850 				// the start.
851 				if ( !startOffset )
852 				{
853 					startOffset = startContainer.getIndex();
854 					startContainer = startContainer.getParent();
855 				}
856 				// If the offset is at the end, we'll insert it after the text
857 				// node.
858 				else if ( startOffset >= startContainer.getLength() )
859 				{
860 					startOffset = startContainer.getIndex() + 1;
861 					startContainer = startContainer.getParent();
862 				}
863 				// In other case, we split the text node and insert the new
864 				// node at the split point.
865 				else
866 				{
867 					var nextText = startContainer.split( startOffset );
868 
869 					startOffset = startContainer.getIndex() + 1;
870 					startContainer = startContainer.getParent();
871 
872 					// Check all necessity of updating the end boundary.
873 					if ( this.startContainer.equals( this.endContainer ) )
874 						this.setEnd( nextText, this.endOffset - this.startOffset );
875 					else if ( startContainer.equals( this.endContainer ) )
876 						this.endOffset += 1;
877 				}
878 
879 				this.setStart( startContainer, startOffset );
880 
881 				if ( collapsed )
882 				{
883 					this.collapse( true );
884 					return;
885 				}
886 			}
887 
888 			var endContainer = this.endContainer;
889 			var endOffset = this.endOffset;
890 
891 			if ( !( ignoreEnd || collapsed )
892 				 && endContainer && endContainer.type == CKEDITOR.NODE_TEXT )
893 			{
894 				// If the offset is zero, we just insert the new node before
895 				// the start.
896 				if ( !endOffset )
897 				{
898 					endOffset = endContainer.getIndex();
899 					endContainer = endContainer.getParent();
900 				}
901 				// If the offset is at the end, we'll insert it after the text
902 				// node.
903 				else if ( endOffset >= endContainer.getLength() )
904 				{
905 					endOffset = endContainer.getIndex() + 1;
906 					endContainer = endContainer.getParent();
907 				}
908 				// In other case, we split the text node and insert the new
909 				// node at the split point.
910 				else
911 				{
912 					endContainer.split( endOffset );
913 
914 					endOffset = endContainer.getIndex() + 1;
915 					endContainer = endContainer.getParent();
916 				}
917 
918 				this.setEnd( endContainer, endOffset );
919 			}
920 		},
921 
922 		/**
923 		 * Expands the range so that partial units are completely contained.
924 		 * @param unit {Number} The unit type to expand with.
925 		 * @param {Boolean} [excludeBrs=false] Whether include line-breaks when expanding.
926 		 */
927 		enlarge : function( unit, excludeBrs )
928 		{
929 			switch ( unit )
930 			{
931 				case CKEDITOR.ENLARGE_ELEMENT :
932 
933 					if ( this.collapsed )
934 						return;
935 
936 					// Get the common ancestor.
937 					var commonAncestor = this.getCommonAncestor();
938 
939 					var body = this.document.getBody();
940 
941 					// For each boundary
942 					//		a. Depending on its position, find out the first node to be checked (a sibling) or, if not available, to be enlarge.
943 					//		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.
944 
945 					var startTop, endTop;
946 
947 					var enlargeable, sibling, commonReached;
948 
949 					// Indicates that the node can be added only if whitespace
950 					// is available before it.
951 					var needsWhiteSpace = false;
952 					var isWhiteSpace;
953 					var siblingText;
954 
955 					// Process the start boundary.
956 
957 					var container = this.startContainer;
958 					var offset = this.startOffset;
959 
960 					if ( container.type == CKEDITOR.NODE_TEXT )
961 					{
962 						if ( offset )
963 						{
964 							// Check if there is any non-space text before the
965 							// offset. Otherwise, container is null.
966 							container = !CKEDITOR.tools.trim( container.substring( 0, offset ) ).length && container;
967 
968 							// If we found only whitespace in the node, it
969 							// means that we'll need more whitespace to be able
970 							// to expand. For example, <i> can be expanded in
971 							// "A <i> [B]</i>", but not in "A<i> [B]</i>".
972 							needsWhiteSpace = !!container;
973 						}
974 
975 						if ( container )
976 						{
977 							if ( !( sibling = container.getPrevious() ) )
978 								enlargeable = container.getParent();
979 						}
980 					}
981 					else
982 					{
983 						// If we have offset, get the node preceeding it as the
984 						// first sibling to be checked.
985 						if ( offset )
986 							sibling = container.getChild( offset - 1 ) || container.getLast();
987 
988 						// If there is no sibling, mark the container to be
989 						// enlarged.
990 						if ( !sibling )
991 							enlargeable = container;
992 					}
993 
994 					while ( enlargeable || sibling )
995 					{
996 						if ( enlargeable && !sibling )
997 						{
998 							// If we reached the common ancestor, mark the flag
999 							// for it.
1000 							if ( !commonReached && enlargeable.equals( commonAncestor ) )
1001 								commonReached = true;
1002 
1003 							if ( !body.contains( enlargeable ) )
1004 								break;
1005 
1006 							// If we don't need space or this element breaks
1007 							// the line, then enlarge it.
1008 							if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
1009 							{
1010 								needsWhiteSpace = false;
1011 
1012 								// If the common ancestor has been reached,
1013 								// we'll not enlarge it immediately, but just
1014 								// mark it to be enlarged later if the end
1015 								// boundary also enlarges it.
1016 								if ( commonReached )
1017 									startTop = enlargeable;
1018 								else
1019 									this.setStartBefore( enlargeable );
1020 							}
1021 
1022 							sibling = enlargeable.getPrevious();
1023 						}
1024 
1025 						// Check all sibling nodes preceeding the enlargeable
1026 						// node. The node wil lbe enlarged only if none of them
1027 						// blocks it.
1028 						while ( sibling )
1029 						{
1030 							// This flag indicates that this node has
1031 							// whitespaces at the end.
1032 							isWhiteSpace = false;
1033 
1034 							if ( sibling.type == CKEDITOR.NODE_COMMENT )
1035 							{
1036 								sibling = sibling.getPrevious();
1037 								continue;
1038 							}
1039 							else if ( sibling.type == CKEDITOR.NODE_TEXT )
1040 							{
1041 								siblingText = sibling.getText();
1042 
1043 								if ( /[^\s\ufeff]/.test( siblingText ) )
1044 									sibling = null;
1045 
1046 								isWhiteSpace = /[\s\ufeff]$/.test( siblingText );
1047 							}
1048 							else
1049 							{
1050 								// If this is a visible element.
1051 								// We need to check for the bookmark attribute because IE insists on
1052 								// rendering the display:none nodes we use for bookmarks. (#3363)
1053 								// Line-breaks (br) are rendered with zero width, which we don't want to include. (#7041)
1054 								if ( ( sibling.$.offsetWidth > 0 || excludeBrs && sibling.is( 'br' ) ) && !sibling.data( 'cke-bookmark' ) )
1055 								{
1056 									// We'll accept it only if we need
1057 									// whitespace, and this is an inline
1058 									// element with whitespace only.
1059 									if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
1060 									{
1061 										// It must contains spaces and inline elements only.
1062 
1063 										siblingText = sibling.getText();
1064 
1065 										if ( (/[^\s\ufeff]/).test( siblingText ) )	// Spaces + Zero Width No-Break Space (U+FEFF)
1066 											sibling = null;
1067 										else
1068 										{
1069 											var allChildren = sibling.$.getElementsByTagName( '*' );
1070 											for ( var i = 0, child ; child = allChildren[ i++ ] ; )
1071 											{
1072 												if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
1073 												{
1074 													sibling = null;
1075 													break;
1076 												}
1077 											}
1078 										}
1079 
1080 										if ( sibling )
1081 											isWhiteSpace = !!siblingText.length;
1082 									}
1083 									else
1084 										sibling = null;
1085 								}
1086 							}
1087 
1088 							// A node with whitespaces has been found.
1089 							if ( isWhiteSpace )
1090 							{
1091 								// Enlarge the last enlargeable node, if we
1092 								// were waiting for spaces.
1093 								if ( needsWhiteSpace )
1094 								{
1095 									if ( commonReached )
1096 										startTop = enlargeable;
1097 									else if ( enlargeable )
1098 										this.setStartBefore( enlargeable );
1099 								}
1100 								else
1101 									needsWhiteSpace = true;
1102 							}
1103 
1104 							if ( sibling )
1105 							{
1106 								var next = sibling.getPrevious();
1107 
1108 								if ( !enlargeable && !next )
1109 								{
1110 									// Set the sibling as enlargeable, so it's
1111 									// parent will be get later outside this while.
1112 									enlargeable = sibling;
1113 									sibling = null;
1114 									break;
1115 								}
1116 
1117 								sibling = next;
1118 							}
1119 							else
1120 							{
1121 								// If sibling has been set to null, then we
1122 								// need to stop enlarging.
1123 								enlargeable = null;
1124 							}
1125 						}
1126 
1127 						if ( enlargeable )
1128 							enlargeable = enlargeable.getParent();
1129 					}
1130 
1131 					// Process the end boundary. This is basically the same
1132 					// code used for the start boundary, with small changes to
1133 					// make it work in the oposite side (to the right). This
1134 					// makes it difficult to reuse the code here. So, fixes to
1135 					// the above code are likely to be replicated here.
1136 
1137 					container = this.endContainer;
1138 					offset = this.endOffset;
1139 
1140 					// Reset the common variables.
1141 					enlargeable = sibling = null;
1142 					commonReached = needsWhiteSpace = false;
1143 
1144 					if ( container.type == CKEDITOR.NODE_TEXT )
1145 					{
1146 						// Check if there is any non-space text after the
1147 						// offset. Otherwise, container is null.
1148 						container = !CKEDITOR.tools.trim( container.substring( offset ) ).length && container;
1149 
1150 						// If we found only whitespace in the node, it
1151 						// means that we'll need more whitespace to be able
1152 						// to expand. For example, <i> can be expanded in
1153 						// "A <i> [B]</i>", but not in "A<i> [B]</i>".
1154 						needsWhiteSpace = !( container && container.getLength() );
1155 
1156 						if ( container )
1157 						{
1158 							if ( !( sibling = container.getNext() ) )
1159 								enlargeable = container.getParent();
1160 						}
1161 					}
1162 					else
1163 					{
1164 						// Get the node right after the boudary to be checked
1165 						// first.
1166 						sibling = container.getChild( offset );
1167 
1168 						if ( !sibling )
1169 							enlargeable = container;
1170 					}
1171 
1172 					while ( enlargeable || sibling )
1173 					{
1174 						if ( enlargeable && !sibling )
1175 						{
1176 							if ( !commonReached && enlargeable.equals( commonAncestor ) )
1177 								commonReached = true;
1178 
1179 							if ( !body.contains( enlargeable ) )
1180 								break;
1181 
1182 							if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
1183 							{
1184 								needsWhiteSpace = false;
1185 
1186 								if ( commonReached )
1187 									endTop = enlargeable;
1188 								else if ( enlargeable )
1189 									this.setEndAfter( enlargeable );
1190 							}
1191 
1192 							sibling = enlargeable.getNext();
1193 						}
1194 
1195 						while ( sibling )
1196 						{
1197 							isWhiteSpace = false;
1198 
1199 							if ( sibling.type == CKEDITOR.NODE_TEXT )
1200 							{
1201 								siblingText = sibling.getText();
1202 
1203 								if ( /[^\s\ufeff]/.test( siblingText ) )
1204 									sibling = null;
1205 
1206 								isWhiteSpace = /^[\s\ufeff]/.test( siblingText );
1207 							}
1208 							else if ( sibling.type == CKEDITOR.NODE_ELEMENT )
1209 							{
1210 								// If this is a visible element.
1211 								// We need to check for the bookmark attribute because IE insists on
1212 								// rendering the display:none nodes we use for bookmarks. (#3363)
1213 								// Line-breaks (br) are rendered with zero width, which we don't want to include. (#7041)
1214 								if ( ( sibling.$.offsetWidth > 0 || excludeBrs && sibling.is( 'br' ) ) && !sibling.data( 'cke-bookmark' ) )
1215 								{
1216 									// We'll accept it only if we need
1217 									// whitespace, and this is an inline
1218 									// element with whitespace only.
1219 									if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
1220 									{
1221 										// It must contains spaces and inline elements only.
1222 
1223 										siblingText = sibling.getText();
1224 
1225 										if ( (/[^\s\ufeff]/).test( siblingText ) )
1226 											sibling = null;
1227 										else
1228 										{
1229 											allChildren = sibling.$.getElementsByTagName( '*' );
1230 											for ( i = 0 ; child = allChildren[ i++ ] ; )
1231 											{
1232 												if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
1233 												{
1234 													sibling = null;
1235 													break;
1236 												}
1237 											}
1238 										}
1239 
1240 										if ( sibling )
1241 											isWhiteSpace = !!siblingText.length;
1242 									}
1243 									else
1244 										sibling = null;
1245 								}
1246 							}
1247 							else
1248 								isWhiteSpace = 1;
1249 
1250 							if ( isWhiteSpace )
1251 							{
1252 								if ( needsWhiteSpace )
1253 								{
1254 									if ( commonReached )
1255 										endTop = enlargeable;
1256 									else
1257 										this.setEndAfter( enlargeable );
1258 								}
1259 							}
1260 
1261 							if ( sibling )
1262 							{
1263 								next = sibling.getNext();
1264 
1265 								if ( !enlargeable && !next )
1266 								{
1267 									enlargeable = sibling;
1268 									sibling = null;
1269 									break;
1270 								}
1271 
1272 								sibling = next;
1273 							}
1274 							else
1275 							{
1276 								// If sibling has been set to null, then we
1277 								// need to stop enlarging.
1278 								enlargeable = null;
1279 							}
1280 						}
1281 
1282 						if ( enlargeable )
1283 							enlargeable = enlargeable.getParent();
1284 					}
1285 
1286 					// If the common ancestor can be enlarged by both boundaries, then include it also.
1287 					if ( startTop && endTop )
1288 					{
1289 						commonAncestor = startTop.contains( endTop ) ? endTop : startTop;
1290 
1291 						this.setStartBefore( commonAncestor );
1292 						this.setEndAfter( commonAncestor );
1293 					}
1294 					break;
1295 
1296 				case CKEDITOR.ENLARGE_BLOCK_CONTENTS:
1297 				case CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS:
1298 
1299 					// Enlarging the start boundary.
1300 					var walkerRange = new CKEDITOR.dom.range( this.document );
1301 
1302 					body = this.document.getBody();
1303 
1304 					walkerRange.setStartAt( body, CKEDITOR.POSITION_AFTER_START );
1305 					walkerRange.setEnd( this.startContainer, this.startOffset );
1306 
1307 					var walker = new CKEDITOR.dom.walker( walkerRange ),
1308 					    blockBoundary,  // The node on which the enlarging should stop.
1309 						tailBr, // In case BR as block boundary.
1310 					    notBlockBoundary = CKEDITOR.dom.walker.blockBoundary(
1311 								( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ? { br : 1 } : null ),
1312 						// Record the encountered 'blockBoundary' for later use.
1313 						boundaryGuard = function( node )
1314 						{
1315 							var retval = notBlockBoundary( node );
1316 							if ( !retval )
1317 								blockBoundary = node;
1318 							return retval;
1319 						},
1320 						// Record the encounted 'tailBr' for later use.
1321 						tailBrGuard = function( node )
1322 						{
1323 							var retval = boundaryGuard( node );
1324 							if ( !retval && node.is && node.is( 'br' ) )
1325 								tailBr = node;
1326 							return retval;
1327 						};
1328 
1329 					walker.guard = boundaryGuard;
1330 
1331 					enlargeable = walker.lastBackward();
1332 
1333 					// It's the body which stop the enlarging if no block boundary found.
1334 					blockBoundary = blockBoundary || body;
1335 
1336 					// Start the range either after the end of found block (<p>...</p>[text)
1337 					// or at the start of block (<p>[text...), by comparing the document position
1338 					// with 'enlargeable' node.
1339 					this.setStartAt(
1340 							blockBoundary,
1341 							!blockBoundary.is( 'br' ) &&
1342 							( !enlargeable && this.checkStartOfBlock()
1343 							  || enlargeable && blockBoundary.contains( enlargeable ) ) ?
1344 								CKEDITOR.POSITION_AFTER_START :
1345 								CKEDITOR.POSITION_AFTER_END );
1346 
1347 					// Avoid enlarging the range further when end boundary spans right after the BR. (#7490)
1348 					if ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS )
1349 					{
1350 						var theRange = this.clone();
1351 						walker = new CKEDITOR.dom.walker( theRange );
1352 
1353 						var whitespaces = CKEDITOR.dom.walker.whitespaces(),
1354 							bookmark = CKEDITOR.dom.walker.bookmark();
1355 
1356 						walker.evaluator = function( node ) { return !whitespaces( node ) && !bookmark( node ); };
1357 						var previous = walker.previous();
1358 						if ( previous && previous.type == CKEDITOR.NODE_ELEMENT && previous.is( 'br' ) )
1359 							return;
1360 					}
1361 
1362 
1363 					// Enlarging the end boundary.
1364 					walkerRange = this.clone();
1365 					walkerRange.collapse();
1366 					walkerRange.setEndAt( body, CKEDITOR.POSITION_BEFORE_END );
1367 					walker = new CKEDITOR.dom.walker( walkerRange );
1368 
1369 					// tailBrGuard only used for on range end.
1370 					walker.guard = ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ?
1371 						tailBrGuard : boundaryGuard;
1372 					blockBoundary = null;
1373 					// End the range right before the block boundary node.
1374 
1375 					enlargeable = walker.lastForward();
1376 
1377 					// It's the body which stop the enlarging if no block boundary found.
1378 					blockBoundary = blockBoundary || body;
1379 
1380 					// Close the range either before the found block start (text]<p>...</p>) or at the block end (...text]</p>)
1381 					// by comparing the document position with 'enlargeable' node.
1382 					this.setEndAt(
1383 							blockBoundary,
1384 							( !enlargeable && this.checkEndOfBlock()
1385 							  || enlargeable && blockBoundary.contains( enlargeable ) ) ?
1386 								CKEDITOR.POSITION_BEFORE_END :
1387 								CKEDITOR.POSITION_BEFORE_START );
1388 					// We must include the <br> at the end of range if there's
1389 					// one and we're expanding list item contents
1390 					if ( tailBr )
1391 						this.setEndAfter( tailBr );
1392 			}
1393 		},
1394 
1395 		/**
1396 		 *  Descrease the range to make sure that boundaries
1397 		*  always anchor beside text nodes or innermost element.
1398 		 * @param {Number} mode  ( CKEDITOR.SHRINK_ELEMENT | CKEDITOR.SHRINK_TEXT ) The shrinking mode.
1399 		 * <dl>
1400 		 * 	 <dt>CKEDITOR.SHRINK_ELEMENT</dt>
1401 		 * 	 <dd>Shrink the range boundaries to the edge of the innermost element.</dd>
1402 		 * 	 <dt>CKEDITOR.SHRINK_TEXT</dt>
1403 		 * 	 <dd>Shrink the range boudaries to anchor by the side of enclosed text  node, range remains if there's no text nodes on boundaries at all.</dd>
1404 		  * </dl>
1405 		 * @param {Boolean} selectContents Whether result range anchors at the inner OR outer boundary of the node.
1406 		 */
1407 		shrink : function( mode, selectContents )
1408 		{
1409 			// Unable to shrink a collapsed range.
1410 			if ( !this.collapsed )
1411 			{
1412 				mode = mode || CKEDITOR.SHRINK_TEXT;
1413 
1414 				var walkerRange = this.clone();
1415 
1416 				var startContainer = this.startContainer,
1417 					endContainer = this.endContainer,
1418 					startOffset = this.startOffset,
1419 					endOffset = this.endOffset,
1420 					collapsed = this.collapsed;
1421 
1422 				// Whether the start/end boundary is moveable.
1423 				var moveStart = 1,
1424 						moveEnd = 1;
1425 
1426 				if ( startContainer && startContainer.type == CKEDITOR.NODE_TEXT )
1427 				{
1428 					if ( !startOffset )
1429 						walkerRange.setStartBefore( startContainer );
1430 					else if ( startOffset >= startContainer.getLength( ) )
1431 						walkerRange.setStartAfter( startContainer );
1432 					else
1433 					{
1434 						// Enlarge the range properly to avoid walker making
1435 						// DOM changes caused by triming the text nodes later.
1436 						walkerRange.setStartBefore( startContainer );
1437 						moveStart = 0;
1438 					}
1439 				}
1440 
1441 				if ( endContainer && endContainer.type == CKEDITOR.NODE_TEXT )
1442 				{
1443 					if ( !endOffset )
1444 						walkerRange.setEndBefore( endContainer );
1445 					else if ( endOffset >= endContainer.getLength( ) )
1446 						walkerRange.setEndAfter( endContainer );
1447 					else
1448 					{
1449 						walkerRange.setEndAfter( endContainer );
1450 						moveEnd = 0;
1451 					}
1452 				}
1453 
1454 				var walker = new CKEDITOR.dom.walker( walkerRange ),
1455 					isBookmark = CKEDITOR.dom.walker.bookmark();
1456 
1457 				walker.evaluator = function( node )
1458 				{
1459 					return node.type == ( mode == CKEDITOR.SHRINK_ELEMENT ?
1460 						CKEDITOR.NODE_ELEMENT : CKEDITOR.NODE_TEXT );
1461 				};
1462 
1463 				var currentElement;
1464 				walker.guard = function( node, movingOut )
1465 				{
1466 					if ( isBookmark( node ) )
1467 						return true;
1468 
1469 					// Stop when we're shrink in element mode while encountering a text node.
1470 					if ( mode == CKEDITOR.SHRINK_ELEMENT && node.type == CKEDITOR.NODE_TEXT )
1471 						return false;
1472 
1473 					// Stop when we've already walked "through" an element.
1474 					if ( movingOut && node.equals( currentElement ) )
1475 						return false;
1476 
1477 					if ( !movingOut && node.type == CKEDITOR.NODE_ELEMENT )
1478 						currentElement = node;
1479 
1480 					return true;
1481 				};
1482 
1483 				if ( moveStart )
1484 				{
1485 					var textStart = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastForward' : 'next']();
1486 					textStart && this.setStartAt( textStart, selectContents ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_START );
1487 				}
1488 
1489 				if ( moveEnd )
1490 				{
1491 					walker.reset();
1492 					var textEnd = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastBackward' : 'previous']();
1493 					textEnd && this.setEndAt( textEnd, selectContents ? CKEDITOR.POSITION_BEFORE_END : CKEDITOR.POSITION_AFTER_END );
1494 				}
1495 
1496 				return !!( moveStart || moveEnd );
1497 			}
1498 		},
1499 
1500 		/**
1501 		 * Inserts a node at the start of the range. The range will be expanded
1502 		 * the contain the node.
1503 		 */
1504 		insertNode : function( node )
1505 		{
1506 			this.optimizeBookmark();
1507 			this.trim( false, true );
1508 
1509 			var startContainer = this.startContainer;
1510 			var startOffset = this.startOffset;
1511 
1512 			var nextNode = startContainer.getChild( startOffset );
1513 
1514 			if ( nextNode )
1515 				node.insertBefore( nextNode );
1516 			else
1517 				startContainer.append( node );
1518 
1519 			// Check if we need to update the end boundary.
1520 			if ( node.getParent().equals( this.endContainer ) )
1521 				this.endOffset++;
1522 
1523 			// Expand the range to embrace the new node.
1524 			this.setStartBefore( node );
1525 		},
1526 
1527 		moveToPosition : function( node, position )
1528 		{
1529 			this.setStartAt( node, position );
1530 			this.collapse( true );
1531 		},
1532 
1533 		selectNodeContents : function( node )
1534 		{
1535 			this.setStart( node, 0 );
1536 			this.setEnd( node, node.type == CKEDITOR.NODE_TEXT ? node.getLength() : node.getChildCount() );
1537 		},
1538 
1539 		/**
1540 		 * Sets the start position of a Range.
1541 		 * @param {CKEDITOR.dom.node} startNode The node to start the range.
1542 		 * @param {Number} startOffset An integer greater than or equal to zero
1543 		 *		representing the offset for the start of the range from the start
1544 		 *		of startNode.
1545 		 */
1546 		setStart : function( startNode, startOffset )
1547 		{
1548 			// W3C requires a check for the new position. If it is after the end
1549 			// boundary, the range should be collapsed to the new start. It seams
1550 			// we will not need this check for our use of this class so we can
1551 			// ignore it for now.
1552 
1553 			// Fixing invalid range start inside dtd empty elements.
1554 			if( startNode.type == CKEDITOR.NODE_ELEMENT
1555 				&& CKEDITOR.dtd.$empty[ startNode.getName() ] )
1556 				startOffset = startNode.getIndex(), startNode = startNode.getParent();
1557 
1558 			this.startContainer	= startNode;
1559 			this.startOffset	= startOffset;
1560 
1561 			if ( !this.endContainer )
1562 			{
1563 				this.endContainer	= startNode;
1564 				this.endOffset		= startOffset;
1565 			}
1566 
1567 			updateCollapsed( this );
1568 		},
1569 
1570 		/**
1571 		 * Sets the end position of a Range.
1572 		 * @param {CKEDITOR.dom.node} endNode The node to end the range.
1573 		 * @param {Number} endOffset An integer greater than or equal to zero
1574 		 *		representing the offset for the end of the range from the start
1575 		 *		of endNode.
1576 		 */
1577 		setEnd : function( endNode, endOffset )
1578 		{
1579 			// W3C requires a check for the new position. If it is before the start
1580 			// boundary, the range should be collapsed to the new end. It seams we
1581 			// will not need this check for our use of this class so we can ignore
1582 			// it for now.
1583 
1584 			// Fixing invalid range end inside dtd empty elements.
1585 			if( endNode.type == CKEDITOR.NODE_ELEMENT
1586 				&& CKEDITOR.dtd.$empty[ endNode.getName() ] )
1587 				endOffset = endNode.getIndex() + 1, endNode = endNode.getParent();
1588 
1589 			this.endContainer	= endNode;
1590 			this.endOffset		= endOffset;
1591 
1592 			if ( !this.startContainer )
1593 			{
1594 				this.startContainer	= endNode;
1595 				this.startOffset	= endOffset;
1596 			}
1597 
1598 			updateCollapsed( this );
1599 		},
1600 
1601 		setStartAfter : function( node )
1602 		{
1603 			this.setStart( node.getParent(), node.getIndex() + 1 );
1604 		},
1605 
1606 		setStartBefore : function( node )
1607 		{
1608 			this.setStart( node.getParent(), node.getIndex() );
1609 		},
1610 
1611 		setEndAfter : function( node )
1612 		{
1613 			this.setEnd( node.getParent(), node.getIndex() + 1 );
1614 		},
1615 
1616 		setEndBefore : function( node )
1617 		{
1618 			this.setEnd( node.getParent(), node.getIndex() );
1619 		},
1620 
1621 		setStartAt : function( node, position )
1622 		{
1623 			switch( position )
1624 			{
1625 				case CKEDITOR.POSITION_AFTER_START :
1626 					this.setStart( node, 0 );
1627 					break;
1628 
1629 				case CKEDITOR.POSITION_BEFORE_END :
1630 					if ( node.type == CKEDITOR.NODE_TEXT )
1631 						this.setStart( node, node.getLength() );
1632 					else
1633 						this.setStart( node, node.getChildCount() );
1634 					break;
1635 
1636 				case CKEDITOR.POSITION_BEFORE_START :
1637 					this.setStartBefore( node );
1638 					break;
1639 
1640 				case CKEDITOR.POSITION_AFTER_END :
1641 					this.setStartAfter( node );
1642 			}
1643 
1644 			updateCollapsed( this );
1645 		},
1646 
1647 		setEndAt : function( node, position )
1648 		{
1649 			switch( position )
1650 			{
1651 				case CKEDITOR.POSITION_AFTER_START :
1652 					this.setEnd( node, 0 );
1653 					break;
1654 
1655 				case CKEDITOR.POSITION_BEFORE_END :
1656 					if ( node.type == CKEDITOR.NODE_TEXT )
1657 						this.setEnd( node, node.getLength() );
1658 					else
1659 						this.setEnd( node, node.getChildCount() );
1660 					break;
1661 
1662 				case CKEDITOR.POSITION_BEFORE_START :
1663 					this.setEndBefore( node );
1664 					break;
1665 
1666 				case CKEDITOR.POSITION_AFTER_END :
1667 					this.setEndAfter( node );
1668 			}
1669 
1670 			updateCollapsed( this );
1671 		},
1672 
1673 		fixBlock : function( isStart, blockTag )
1674 		{
1675 			var bookmark = this.createBookmark(),
1676 				fixedBlock = this.document.createElement( blockTag );
1677 
1678 			this.collapse( isStart );
1679 
1680 			this.enlarge( CKEDITOR.ENLARGE_BLOCK_CONTENTS );
1681 
1682 			this.extractContents().appendTo( fixedBlock );
1683 			fixedBlock.trim();
1684 
1685 			if ( !CKEDITOR.env.ie )
1686 				fixedBlock.appendBogus();
1687 
1688 			this.insertNode( fixedBlock );
1689 
1690 			this.moveToBookmark( bookmark );
1691 
1692 			return fixedBlock;
1693 		},
1694 
1695 		splitBlock : function( blockTag )
1696 		{
1697 			var startPath	= new CKEDITOR.dom.elementPath( this.startContainer ),
1698 				endPath		= new CKEDITOR.dom.elementPath( this.endContainer );
1699 
1700 			var startBlockLimit	= startPath.blockLimit,
1701 				endBlockLimit	= endPath.blockLimit;
1702 
1703 			var startBlock	= startPath.block,
1704 				endBlock	= endPath.block;
1705 
1706 			var elementPath = null;
1707 			// Do nothing if the boundaries are in different block limits.
1708 			if ( !startBlockLimit.equals( endBlockLimit ) )
1709 				return null;
1710 
1711 			// Get or fix current blocks.
1712 			if ( blockTag != 'br' )
1713 			{
1714 				if ( !startBlock )
1715 				{
1716 					startBlock = this.fixBlock( true, blockTag );
1717 					endBlock = new CKEDITOR.dom.elementPath( this.endContainer ).block;
1718 				}
1719 
1720 				if ( !endBlock )
1721 					endBlock = this.fixBlock( false, blockTag );
1722 			}
1723 
1724 			// Get the range position.
1725 			var isStartOfBlock = startBlock && this.checkStartOfBlock(),
1726 				isEndOfBlock = endBlock && this.checkEndOfBlock();
1727 
1728 			// Delete the current contents.
1729 			// TODO: Why is 2.x doing CheckIsEmpty()?
1730 			this.deleteContents();
1731 
1732 			if ( startBlock && startBlock.equals( endBlock ) )
1733 			{
1734 				if ( isEndOfBlock )
1735 				{
1736 					elementPath = new CKEDITOR.dom.elementPath( this.startContainer );
1737 					this.moveToPosition( endBlock, CKEDITOR.POSITION_AFTER_END );
1738 					endBlock = null;
1739 				}
1740 				else if ( isStartOfBlock )
1741 				{
1742 					elementPath = new CKEDITOR.dom.elementPath( this.startContainer );
1743 					this.moveToPosition( startBlock, CKEDITOR.POSITION_BEFORE_START );
1744 					startBlock = null;
1745 				}
1746 				else
1747 				{
1748 					endBlock = this.splitElement( startBlock );
1749 
1750 					// In Gecko, the last child node must be a bogus <br>.
1751 					// Note: bogus <br> added under <ul> or <ol> would cause
1752 					// lists to be incorrectly rendered.
1753 					if ( !CKEDITOR.env.ie && !startBlock.is( 'ul', 'ol') )
1754 						startBlock.appendBogus() ;
1755 				}
1756 			}
1757 
1758 			return {
1759 				previousBlock : startBlock,
1760 				nextBlock : endBlock,
1761 				wasStartOfBlock : isStartOfBlock,
1762 				wasEndOfBlock : isEndOfBlock,
1763 				elementPath : elementPath
1764 			};
1765 		},
1766 
1767 		/**
1768 		 * Branch the specified element from the collapsed range position and
1769 		 * place the caret between the two result branches.
1770 		 * Note: The range must be collapsed and been enclosed by this element.
1771 		 * @param {CKEDITOR.dom.element} element
1772 		 * @return {CKEDITOR.dom.element} Root element of the new branch after the split.
1773 		 */
1774 		splitElement : function( toSplit )
1775 		{
1776 			if ( !this.collapsed )
1777 				return null;
1778 
1779 			// Extract the contents of the block from the selection point to the end
1780 			// of its contents.
1781 			this.setEndAt( toSplit, CKEDITOR.POSITION_BEFORE_END );
1782 			var documentFragment = this.extractContents();
1783 
1784 			// Duplicate the element after it.
1785 			var clone = toSplit.clone( false );
1786 
1787 			// Place the extracted contents into the duplicated element.
1788 			documentFragment.appendTo( clone );
1789 			clone.insertAfter( toSplit );
1790 			this.moveToPosition( toSplit, CKEDITOR.POSITION_AFTER_END );
1791 			return clone;
1792 		},
1793 
1794 		/**
1795 		 * Check whether a range boundary is at the inner boundary of a given
1796 		 * element.
1797 		 * @param {CKEDITOR.dom.element} element The target element to check.
1798 		 * @param {Number} checkType The boundary to check for both the range
1799 		 *		and the element. It can be CKEDITOR.START or CKEDITOR.END.
1800 		 * @returns {Boolean} "true" if the range boundary is at the inner
1801 		 *		boundary of the element.
1802 		 */
1803 		checkBoundaryOfElement : function( element, checkType )
1804 		{
1805 			var checkStart = ( checkType == CKEDITOR.START );
1806 
1807 			// Create a copy of this range, so we can manipulate it for our checks.
1808 			var walkerRange = this.clone();
1809 
1810 			// Collapse the range at the proper size.
1811 			walkerRange.collapse( checkStart );
1812 
1813 			// Expand the range to element boundary.
1814 			walkerRange[ checkStart ? 'setStartAt' : 'setEndAt' ]
1815 			 ( element, checkStart ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_END );
1816 
1817 			// Create the walker, which will check if we have anything useful
1818 			// in the range.
1819 			var walker = new CKEDITOR.dom.walker( walkerRange );
1820 			walker.evaluator = elementBoundaryEval( checkStart );
1821 
1822 			return walker[ checkStart ? 'checkBackward' : 'checkForward' ]();
1823 		},
1824 
1825 		// Calls to this function may produce changes to the DOM. The range may
1826 		// be updated to reflect such changes.
1827 		checkStartOfBlock : function()
1828 		{
1829 			var startContainer = this.startContainer,
1830 				startOffset = this.startOffset;
1831 
1832 			// [IE] Special handling for range start in text with a leading NBSP,
1833 			// we it to be isolated, for bogus check.
1834 			if ( CKEDITOR.env.ie && startOffset && startContainer.type == CKEDITOR.NODE_TEXT )
1835 			{
1836 				var textBefore = CKEDITOR.tools.ltrim( startContainer.substring( 0, startOffset ) );
1837 				if ( nbspRegExp.test( textBefore ) )
1838 					this.trim( 0, 1 );
1839 			}
1840 
1841 			// We need to grab the block element holding the start boundary, so
1842 			// let's use an element path for it.
1843 			var path = new CKEDITOR.dom.elementPath( this.startContainer );
1844 
1845 			// Creates a range starting at the block start until the range start.
1846 			var walkerRange = this.clone();
1847 			walkerRange.collapse( true );
1848 			walkerRange.setStartAt( path.block || path.blockLimit, CKEDITOR.POSITION_AFTER_START );
1849 
1850 			var walker = new CKEDITOR.dom.walker( walkerRange );
1851 			walker.evaluator = getCheckStartEndBlockEvalFunction();
1852 
1853 			return walker.checkBackward();
1854 		},
1855 
1856 		checkEndOfBlock : function()
1857 		{
1858 			var endContainer = this.endContainer,
1859 				endOffset = this.endOffset;
1860 
1861 			// [IE] Special handling for range end in text with a following NBSP,
1862 			// we it to be isolated, for bogus check.
1863 			if ( CKEDITOR.env.ie && endContainer.type == CKEDITOR.NODE_TEXT )
1864 			{
1865 				var textAfter = CKEDITOR.tools.rtrim( endContainer.substring( endOffset ) );
1866 				if ( nbspRegExp.test( textAfter ) )
1867 					this.trim( 1, 0 );
1868 			}
1869 
1870 			// We need to grab the block element holding the start boundary, so
1871 			// let's use an element path for it.
1872 			var path = new CKEDITOR.dom.elementPath( this.endContainer );
1873 
1874 			// Creates a range starting at the block start until the range start.
1875 			var walkerRange = this.clone();
1876 			walkerRange.collapse( false );
1877 			walkerRange.setEndAt( path.block || path.blockLimit, CKEDITOR.POSITION_BEFORE_END );
1878 
1879 			var walker = new CKEDITOR.dom.walker( walkerRange );
1880 			walker.evaluator = getCheckStartEndBlockEvalFunction();
1881 
1882 			return walker.checkForward();
1883 		},
1884 
1885 		/**
1886 		 * Traverse with {@link CKEDITOR.dom.walker} to retrieve the previous element before the range start.
1887 		 * @param {Function} evaluator Function used as the walker's evaluator.
1888 		 * @param {Function} [guard] Function used as the walker's guard.
1889 		 * @param {CKEDITOR.dom.element} [boundary] A range ancestor element in which the traversal is limited,
1890 		 * default to the root editable if not defined.
1891 		 *
1892 		 * @return {CKEDITOR.dom.element|null} The returned node from the traversal.
1893 		 */
1894 		getPreviousNode : function( evaluator, guard, boundary ) {
1895 
1896 			var walkerRange = this.clone();
1897 			walkerRange.collapse( 1 );
1898 			walkerRange.setStartAt( boundary || this.document.getBody(), CKEDITOR.POSITION_AFTER_START );
1899 
1900 			var walker = new CKEDITOR.dom.walker( walkerRange );
1901 			walker.evaluator = evaluator;
1902 			walker.guard = guard;
1903 			return walker.previous();
1904 		},
1905 
1906 		/**
1907 		 * Traverse with {@link CKEDITOR.dom.walker} to retrieve the next element before the range start.
1908 		 * @param {Function} evaluator Function used as the walker's evaluator.
1909 		 * @param {Function} [guard] Function used as the walker's guard.
1910 		 * @param {CKEDITOR.dom.element} [boundary] A range ancestor element in which the traversal is limited,
1911 		 * default to the root editable if not defined.
1912 		 *
1913 		 * @return {CKEDITOR.dom.element|null} The returned node from the traversal.
1914 		 */
1915 		getNextNode: function( evaluator, guard, boundary )
1916 		{
1917 			var walkerRange = this.clone();
1918 			walkerRange.collapse();
1919 			walkerRange.setEndAt( boundary || this.document.getBody(), CKEDITOR.POSITION_BEFORE_END );
1920 
1921 			var walker = new CKEDITOR.dom.walker( walkerRange );
1922 			walker.evaluator = evaluator;
1923 			walker.guard = guard;
1924 			return walker.next();
1925 		},
1926 
1927 		checkReadOnly : ( function()
1928 		{
1929 			function checkNodesEditable( node, anotherEnd )
1930 			{
1931 				while( node )
1932 				{
1933 					if ( node.type == CKEDITOR.NODE_ELEMENT )
1934 					{
1935 						if ( node.getAttribute( 'contentEditable' ) == 'false'
1936 							&& !node.data( 'cke-editable' ) )
1937 						{
1938 							return 0;
1939 						}
1940 						// Range enclosed entirely in an editable element.
1941 						else if ( node.is( 'html' )
1942 							|| node.getAttribute( 'contentEditable' ) == 'true'
1943 							&& ( node.contains( anotherEnd ) || node.equals( anotherEnd ) ) )
1944 						{
1945 							break;
1946 						}
1947 					}
1948 					node = node.getParent();
1949 				}
1950 
1951 				return 1;
1952 			}
1953 
1954 			return function()
1955 			{
1956 				var startNode = this.startContainer,
1957 					endNode = this.endContainer;
1958 
1959 				// Check if elements path at both boundaries are editable.
1960 				return !( checkNodesEditable( startNode, endNode ) && checkNodesEditable( endNode, startNode ) );
1961 			};
1962 		})(),
1963 
1964 		/**
1965 		 * Moves the range boundaries to the first/end editing point inside an
1966 		 * element. For example, in an element tree like
1967 		 * "<p><b><i></i></b> Text</p>", the start editing point is
1968 		 * "<p><b><i>^</i></b> Text</p>" (inside <i>).
1969 		 * @param {CKEDITOR.dom.element} el The element into which look for the
1970 		 *		editing spot.
1971 		 * @param {Boolean} isMoveToEnd Whether move to the end editable position.
1972 		 */
1973 		moveToElementEditablePosition : function( el, isMoveToEnd )
1974 		{
1975 			function nextDFS( node, childOnly )
1976 			{
1977 				var next;
1978 
1979 				if ( node.type == CKEDITOR.NODE_ELEMENT && node.isEditable( false ) )
1980 					next = node[ isMoveToEnd ? 'getLast' : 'getFirst' ]( nonWhitespaceOrBookmarkEval );
1981 
1982 				if ( !childOnly && !next )
1983 					next = node[ isMoveToEnd ? 'getPrevious' : 'getNext' ]( nonWhitespaceOrBookmarkEval );
1984 
1985 				return next;
1986 			}
1987 
1988 			// Handle non-editable element e.g. HR.
1989 			if ( el.type == CKEDITOR.NODE_ELEMENT && !el.isEditable( false ) )
1990 			{
1991 				this.moveToPosition( el, isMoveToEnd ?
1992 										 CKEDITOR.POSITION_AFTER_END :
1993 										 CKEDITOR.POSITION_BEFORE_START );
1994 				return true;
1995 			}
1996 
1997 			var found = 0;
1998 
1999 			while ( el )
2000 			{
2001 				// Stop immediately if we've found a text node.
2002 				if ( el.type == CKEDITOR.NODE_TEXT )
2003 				{
2004 					// Put cursor before block filler.
2005 					if ( isMoveToEnd && this.checkEndOfBlock() && nbspRegExp.test( el.getText() ) )
2006 						this.moveToPosition( el, CKEDITOR.POSITION_BEFORE_START );
2007 					else
2008 						this.moveToPosition( el, isMoveToEnd ?
2009 					                         CKEDITOR.POSITION_AFTER_END :
2010 					                         CKEDITOR.POSITION_BEFORE_START );
2011 					found = 1;
2012 					break;
2013 				}
2014 
2015 				// If an editable element is found, move inside it, but not stop the searching.
2016 				if ( el.type == CKEDITOR.NODE_ELEMENT )
2017 				{
2018 					if ( el.isEditable() )
2019 					{
2020 						this.moveToPosition( el, isMoveToEnd ?
2021 												 CKEDITOR.POSITION_BEFORE_END :
2022 												 CKEDITOR.POSITION_AFTER_START );
2023 						found = 1;
2024 					}
2025 					// Put cursor before padding block br.
2026 					else if ( isMoveToEnd && el.is( 'br' ) && this.checkEndOfBlock() )
2027 						this.moveToPosition( el, CKEDITOR.POSITION_BEFORE_START );
2028 				}
2029 
2030 				el = nextDFS( el, found );
2031 			}
2032 
2033 			return !!found;
2034 		},
2035 
2036 		/**
2037 		 *@see {CKEDITOR.dom.range.moveToElementEditablePosition}
2038 		 */
2039 		moveToElementEditStart : function( target )
2040 		{
2041 			return this.moveToElementEditablePosition( target );
2042 		},
2043 
2044 		/**
2045 		 *@see {CKEDITOR.dom.range.moveToElementEditablePosition}
2046 		 */
2047 		moveToElementEditEnd : function( target )
2048 		{
2049 			return this.moveToElementEditablePosition( target, true );
2050 		},
2051 
2052 		/**
2053 		 * Get the single node enclosed within the range if there's one.
2054 		 */
2055 		getEnclosedNode : function()
2056 		{
2057 			var walkerRange = this.clone();
2058 
2059 			// Optimize and analyze the range to avoid DOM destructive nature of walker. (#5780)
2060 			walkerRange.optimize();
2061 			if ( walkerRange.startContainer.type != CKEDITOR.NODE_ELEMENT
2062 					|| walkerRange.endContainer.type != CKEDITOR.NODE_ELEMENT )
2063 				return null;
2064 
2065 			var walker = new CKEDITOR.dom.walker( walkerRange ),
2066 				isNotBookmarks = CKEDITOR.dom.walker.bookmark( true ),
2067 				isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ),
2068 				evaluator = function( node )
2069 				{
2070 					return isNotWhitespaces( node ) && isNotBookmarks( node );
2071 				};
2072 			walkerRange.evaluator = evaluator;
2073 			var node = walker.next();
2074 			walker.reset();
2075 			return node && node.equals( walker.previous() ) ? node : null;
2076 		},
2077 
2078 		getTouchedStartNode : function()
2079 		{
2080 			var container = this.startContainer ;
2081 
2082 			if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT )
2083 				return container ;
2084 
2085 			return container.getChild( this.startOffset ) || container ;
2086 		},
2087 
2088 		getTouchedEndNode : function()
2089 		{
2090 			var container = this.endContainer ;
2091 
2092 			if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT )
2093 				return container ;
2094 
2095 			return container.getChild( this.endOffset - 1 ) || container ;
2096 		}
2097 	};
2098 })();
2099 
2100 CKEDITOR.POSITION_AFTER_START	= 1;	// <element>^contents</element>		"^text"
2101 CKEDITOR.POSITION_BEFORE_END	= 2;	// <element>contents^</element>		"text^"
2102 CKEDITOR.POSITION_BEFORE_START	= 3;	// ^<element>contents</element>		^"text"
2103 CKEDITOR.POSITION_AFTER_END		= 4;	// <element>contents</element>^		"text"
2104 
2105 CKEDITOR.ENLARGE_ELEMENT = 1;
2106 CKEDITOR.ENLARGE_BLOCK_CONTENTS = 2;
2107 CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3;
2108 
2109 // Check boundary types.
2110 // @see CKEDITOR.dom.range.prototype.checkBoundaryOfElement
2111 CKEDITOR.START = 1;
2112 CKEDITOR.END = 2;
2113 CKEDITOR.STARTEND = 3;
2114 
2115 // Shrink range types.
2116 // @see CKEDITOR.dom.range.prototype.shrink
2117 CKEDITOR.SHRINK_ELEMENT = 1;
2118 CKEDITOR.SHRINK_TEXT = 2;
2119