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