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