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