JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.3.2
[ckeditor.git] / _source / core / dom / range.js
index 8d30b4f..3d8b10f 100644 (file)
@@ -697,7 +697,7 @@ CKEDITOR.dom.range = function( document )
                },\r
 \r
                /**\r
-                * Move the range out of bookmark nodes if they're been the container.\r
+                * Move the range out of bookmark nodes if they'd been the container.\r
                 */\r
                optimizeBookmark: function()\r
                {\r
@@ -925,7 +925,7 @@ CKEDITOR.dom.range = function( document )
 \r
                                                                                siblingText = sibling.getText();\r
 \r
-                                                                               if ( !(/[^\s\ufeff]/).test( siblingText ) )     // Spaces + Zero Width No-Break Space (U+FEFF)\r
+                                                                               if ( (/[^\s\ufeff]/).test( siblingText ) )      // Spaces + Zero Width No-Break Space (U+FEFF)\r
                                                                                        sibling = null;\r
                                                                                else\r
                                                                                {\r
@@ -1084,7 +1084,7 @@ CKEDITOR.dom.range = function( document )
 \r
                                                                                siblingText = sibling.getText();\r
 \r
-                                                                               if ( !(/[^\s\ufeff]/).test( siblingText ) )\r
+                                                                               if ( (/[^\s\ufeff]/).test( siblingText ) )\r
                                                                                        sibling = null;\r
                                                                                else\r
                                                                                {\r
@@ -1166,13 +1166,13 @@ CKEDITOR.dom.range = function( document )
 \r
                                        var walker = new CKEDITOR.dom.walker( walkerRange ),\r
                                            blockBoundary,  // The node on which the enlarging should stop.\r
-                                               tailBr, //\r
-                                           defaultGuard = CKEDITOR.dom.walker.blockBoundary(\r
+                                               tailBr, // In case BR as block boundary.\r
+                                           notBlockBoundary = CKEDITOR.dom.walker.blockBoundary(\r
                                                                ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ? { br : 1 } : null ),\r
                                                // Record the encountered 'blockBoundary' for later use.\r
                                                boundaryGuard = function( node )\r
                                                {\r
-                                                       var retval = defaultGuard( node );\r
+                                                       var retval = notBlockBoundary( node );\r
                                                        if ( !retval )\r
                                                                blockBoundary = node;\r
                                                        return retval;\r
@@ -1193,8 +1193,9 @@ CKEDITOR.dom.range = function( document )
                                        // It's the body which stop the enlarging if no block boundary found.\r
                                        blockBoundary = blockBoundary || body;\r
 \r
-                                       // Start the range at different position by comparing\r
-                                       // the document position of it with 'enlargeable' node.\r
+                                       // Start the range either after the end of found block (<p>...</p>[text)\r
+                                       // or at the start of block (<p>[text...), by comparing the document position\r
+                                       // with 'enlargeable' node.\r
                                        this.setStartAt(\r
                                                        blockBoundary,\r
                                                        !blockBoundary.is( 'br' ) &&\r
@@ -1220,8 +1221,8 @@ CKEDITOR.dom.range = function( document )
                                        // It's the body which stop the enlarging if no block boundary found.\r
                                        blockBoundary = blockBoundary || body;\r
 \r
-                                       // Start the range at different position by comparing\r
-                                       // the document position of it with 'enlargeable' node.\r
+                                       // Close the range either before the found block start (text]<p>...</p>) or at the block end (...text]</p>)\r
+                                       // by comparing the document position with 'enlargeable' node.\r
                                        this.setEndAt(\r
                                                        blockBoundary,\r
                                                        ( !enlargeable && this.checkEndOfBlock()\r
@@ -1236,6 +1237,107 @@ CKEDITOR.dom.range = function( document )
                },\r
 \r
                /**\r
+                *  Descrease the range to make sure that boundaries\r
+               *  always anchor beside text nodes or innermost element.\r
+                * @param {Number} mode  ( CKEDITOR.SHRINK_ELEMENT | CKEDITOR.SHRINK_TEXT ) The shrinking mode.\r
+                * <dl>\r
+                *       <dt>CKEDITOR.SHRINK_ELEMENT</dt>\r
+                *       <dd>Shrink the range boundaries to the edge of the innermost element.</dd>\r
+                *       <dt>CKEDITOR.SHRINK_TEXT</dt>\r
+                *       <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
+                 * </dl>\r
+                * @param {Boolean} selectContents Whether result range anchors at the inner OR outer boundary of the node.\r
+                */\r
+               shrink : function( mode, selectContents )\r
+               {\r
+                       // Unable to shrink a collapsed range.\r
+                       if ( !this.collapsed )\r
+                       {\r
+                               mode = mode || CKEDITOR.SHRINK_TEXT;\r
+\r
+                               var walkerRange = this.clone();\r
+\r
+                               var startContainer = this.startContainer,\r
+                                       endContainer = this.endContainer,\r
+                                       startOffset = this.startOffset,\r
+                                       endOffset = this.endOffset,\r
+                                       collapsed = this.collapsed;\r
+\r
+                               // Whether the start/end boundary is moveable.\r
+                               var moveStart = 1,\r
+                                               moveEnd = 1;\r
+\r
+                               if ( startContainer && startContainer.type == CKEDITOR.NODE_TEXT )\r
+                               {\r
+                                       if ( !startOffset )\r
+                                               walkerRange.setStartBefore( startContainer );\r
+                                       else if ( startOffset >= startContainer.getLength( ) )\r
+                                               walkerRange.setStartAfter( startContainer );\r
+                                       else\r
+                                       {\r
+                                               // Enlarge the range properly to avoid walker making\r
+                                               // DOM changes caused by triming the text nodes later.\r
+                                               walkerRange.setStartBefore( startContainer );\r
+                                               moveStart = 0;\r
+                                       }\r
+                               }\r
+\r
+                               if ( endContainer && endContainer.type == CKEDITOR.NODE_TEXT )\r
+                               {\r
+                                       if ( !endOffset )\r
+                                               walkerRange.setEndBefore( endContainer );\r
+                                       else if ( endOffset >= endContainer.getLength( ) )\r
+                                               walkerRange.setEndAfter( endContainer );\r
+                                       else\r
+                                       {\r
+                                               walkerRange.setEndAfter( endContainer );\r
+                                               moveEnd = 0;\r
+                                       }\r
+                               }\r
+\r
+                               var walker = new CKEDITOR.dom.walker( walkerRange );\r
+\r
+                               walker.evaluator = function( node )\r
+                               {\r
+                                       return node.type == ( mode == CKEDITOR.SHRINK_ELEMENT ?\r
+                                               CKEDITOR.NODE_ELEMENT : CKEDITOR.NODE_TEXT );\r
+                               };\r
+\r
+                               var currentElement;\r
+                               walker.guard = function( node, movingOut )\r
+                               {\r
+                                       // Stop when we're shrink in element mode while encountering a text node.\r
+                                       if ( mode == CKEDITOR.SHRINK_ELEMENT && node.type == CKEDITOR.NODE_TEXT )\r
+                                               return false;\r
+\r
+                                       // Stop when we've already walked "through" an element.\r
+                                       if ( movingOut && node.equals( currentElement ) )\r
+                                               return false;\r
+\r
+                                       if ( !movingOut && node.type == CKEDITOR.NODE_ELEMENT )\r
+                                               currentElement = node;\r
+\r
+                                       return true;\r
+                               };\r
+\r
+                               if ( moveStart )\r
+                               {\r
+                                       var textStart = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastForward' : 'next']();\r
+                                       textStart && this.setStartAt( textStart, selectContents ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_START );\r
+                               }\r
+\r
+                               if ( moveEnd )\r
+                               {\r
+                                       walker.reset();\r
+                                       var textEnd = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastBackward' : 'previous']();\r
+                                       textEnd && this.setEndAt( textEnd, selectContents ? CKEDITOR.POSITION_BEFORE_END : CKEDITOR.POSITION_AFTER_END );\r
+                               }\r
+\r
+                               return !!( moveStart || moveEnd );\r
+                       }\r
+               },\r
+\r
+               /**\r
                 * Inserts a node at the start of the range. The range will be expanded\r
                 * the contain the node.\r
                 */\r
@@ -1288,6 +1390,11 @@ CKEDITOR.dom.range = function( document )
                        // we will not need this check for our use of this class so we can\r
                        // ignore it for now.\r
 \r
+                       // Fixing invalid range start inside dtd empty elements.\r
+                       if( startNode.type == CKEDITOR.NODE_ELEMENT\r
+                               && CKEDITOR.dtd.$empty[ startNode.getName() ] )\r
+                               startNode = startNode.getParent(), startOffset = startNode.getIndex();\r
+\r
                        this.startContainer     = startNode;\r
                        this.startOffset        = startOffset;\r
 \r
@@ -1314,6 +1421,11 @@ CKEDITOR.dom.range = function( document )
                        // will not need this check for our use of this class so we can ignore\r
                        // it for now.\r
 \r
+                       // Fixing invalid range end inside dtd empty elements.\r
+                       if( endNode.type == CKEDITOR.NODE_ELEMENT\r
+                               && CKEDITOR.dtd.$empty[ endNode.getName() ] )\r
+                               endNode = endNode.getParent(), endOffset = endNode.getIndex() + 1;\r
+\r
                        this.endContainer       = endNode;\r
                        this.endOffset          = endOffset;\r
 \r
@@ -1534,12 +1646,12 @@ CKEDITOR.dom.range = function( document )
                           CKEDITOR.POSITION_AFTER_START\r
                           : CKEDITOR.POSITION_BEFORE_END );\r
 \r
-                       var walker = new CKEDITOR.dom.walker( walkerRange ),\r
-                        retval = false;\r
+                       var walker = new CKEDITOR.dom.walker( walkerRange );\r
                        walker.evaluator = elementBoundaryEval;\r
                        return walker[ checkType == CKEDITOR.START ?\r
                                'checkBackward' : 'checkForward' ]();\r
                },\r
+\r
                // Calls to this function may produce changes to the DOM. The range may\r
                // be updated to reflect such changes.\r
                checkStartOfBlock : function()\r
@@ -1623,6 +1735,10 @@ CKEDITOR.dom.range = function( document )
                {\r
                        var isEditable;\r
 \r
+                       // Empty elements are rejected.\r
+                       if ( CKEDITOR.dtd.$empty[ el.getName() ] )\r
+                               return false;\r
+\r
                        while ( el && el.type == CKEDITOR.NODE_ELEMENT )\r
                        {\r
                                isEditable = el.isEditable();\r
@@ -1681,8 +1797,15 @@ CKEDITOR.dom.range = function( document )
                 */\r
                getEnclosedNode : function()\r
                {\r
-                       var walkerRange = this.clone(),\r
-                               walker = new CKEDITOR.dom.walker( walkerRange ),\r
+                       var walkerRange = this.clone();\r
+\r
+                       // Optimize and analyze the range to avoid DOM destructive nature of walker. (#5780)\r
+                       walkerRange.optimize();\r
+                       if ( walkerRange.startContainer.type != CKEDITOR.NODE_ELEMENT\r
+                                       || walkerRange.endContainer.type != CKEDITOR.NODE_ELEMENT )\r
+                               return null;\r
+\r
+                       var walker = new CKEDITOR.dom.walker( walkerRange ),\r
                                isNotBookmarks = CKEDITOR.dom.walker.bookmark( true ),\r
                                isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ),\r
                                evaluator = function( node )\r
@@ -1728,8 +1851,15 @@ CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3;
 \r
 /**\r
  * Check boundary types.\r
- * @see CKEDITOR.dom.range::checkBoundaryOfElement\r
+ * @see CKEDITOR.dom.range.prototype.checkBoundaryOfElement\r
  */\r
 CKEDITOR.START = 1;\r
 CKEDITOR.END = 2;\r
 CKEDITOR.STARTEND = 3;\r
+\r
+/**\r
+ * Shrink range types.\r
+ * @see CKEDITOR.dom.range.prototype.shrink\r
+ */\r
+CKEDITOR.SHRINK_ELEMENT = 1;\r
+CKEDITOR.SHRINK_TEXT = 2;\r