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