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