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