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