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