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