JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.6.6.1
[ckeditor.git] / _source / core / dom / range.js
index c187400..fbbbe35 100644 (file)
@@ -1,16 +1,90 @@
 /*\r
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.\r
+Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.\r
 For licensing, see LICENSE.html or http://ckeditor.com/license\r
 */\r
 \r
+/**\r
+ * Creates a CKEDITOR.dom.range instance that can be used inside a specific\r
+ * DOM Document.\r
+ * @class Represents a delimited piece of content in a DOM Document.\r
+ * It is contiguous in the sense that it can be characterized as selecting all\r
+ * of the content between a pair of boundary-points.<br>\r
+ * <br>\r
+ * This class shares much of the W3C\r
+ * <a href="http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html">Document Object Model Range</a>\r
+ * ideas and features, adding several range manipulation tools to it, but it's\r
+ * not intended to be compatible with it.\r
+ * @param {CKEDITOR.dom.document} document The document into which the range\r
+ *             features will be available.\r
+ * @example\r
+ * // Create a range for the entire contents of the editor document body.\r
+ * var range = new CKEDITOR.dom.range( editor.document );\r
+ * range.selectNodeContents( editor.document.getBody() );\r
+ * // Delete the contents.\r
+ * range.deleteContents();\r
+ */\r
 CKEDITOR.dom.range = function( document )\r
 {\r
+       /**\r
+        * Node within which the range begins.\r
+        * @type {CKEDITOR.NODE_ELEMENT|CKEDITOR.NODE_TEXT}\r
+        * @example\r
+        * var range = new CKEDITOR.dom.range( editor.document );\r
+        * range.selectNodeContents( editor.document.getBody() );\r
+        * alert( range.startContainer.getName() );  // "body"\r
+        */\r
        this.startContainer     = null;\r
+\r
+       /**\r
+        * Offset within the starting node of the range.\r
+        * @type {Number}\r
+        * @example\r
+        * var range = new CKEDITOR.dom.range( editor.document );\r
+        * range.selectNodeContents( editor.document.getBody() );\r
+        * alert( range.startOffset );  // "0"\r
+        */\r
        this.startOffset        = null;\r
+\r
+       /**\r
+        * Node within which the range ends.\r
+        * @type {CKEDITOR.NODE_ELEMENT|CKEDITOR.NODE_TEXT}\r
+        * @example\r
+        * var range = new CKEDITOR.dom.range( editor.document );\r
+        * range.selectNodeContents( editor.document.getBody() );\r
+        * alert( range.endContainer.getName() );  // "body"\r
+        */\r
        this.endContainer       = null;\r
+\r
+       /**\r
+        * Offset within the ending node of the range.\r
+        * @type {Number}\r
+        * @example\r
+        * var range = new CKEDITOR.dom.range( editor.document );\r
+        * range.selectNodeContents( editor.document.getBody() );\r
+        * alert( range.endOffset );  // == editor.document.getBody().getChildCount()\r
+        */\r
        this.endOffset          = null;\r
+\r
+       /**\r
+        * Indicates that this is a collapsed range. A collapsed range has it's\r
+        * start and end boudaries at the very same point so nothing is contained\r
+        * in it.\r
+        * @example\r
+        * var range = new CKEDITOR.dom.range( editor.document );\r
+        * range.selectNodeContents( editor.document.getBody() );\r
+        * alert( range.collapsed );  // "false"\r
+        * range.collapse();\r
+        * alert( range.collapsed );  // "true"\r
+        */\r
        this.collapsed          = true;\r
 \r
+       /**\r
+        * The document within which the range can be used.\r
+        * @type {CKEDITOR.dom.document}\r
+        * @example\r
+        * // Selects the body contents of the range document.\r
+        * range.selectNodeContents( range.document.getBody() );\r
+        */\r
        this.document = document;\r
 };\r
 \r
@@ -29,7 +103,7 @@ CKEDITOR.dom.range = function( document )
        // This is a shared function used to delete, extract and clone the range\r
        // contents.\r
        // V2\r
-       var execContentsAction = function( range, action, docFrag )\r
+       var execContentsAction = function( range, action, docFrag, mergeThen )\r
        {\r
                range.optimizeBookmark();\r
 \r
@@ -244,7 +318,17 @@ CKEDITOR.dom.range = function( document )
                                if ( removeStartNode && topEnd.$.parentNode == startNode.$.parentNode )\r
                                        endIndex--;\r
 \r
-                               range.setStart( topEnd.getParent(), endIndex );\r
+                               // Merge splitted parents.\r
+                               if ( mergeThen && topStart.type == CKEDITOR.NODE_ELEMENT )\r
+                               {\r
+                                       var span = CKEDITOR.dom.element.createFromHtml( '<span ' +\r
+                                               'data-cke-bookmark="1" style="display:none">&nbsp;</span>', range.document );\r
+                                       span.insertAfter( topStart );\r
+                                       topStart.mergeSiblings( false );\r
+                                       range.moveToBookmark( { startNode : span } );\r
+                               }\r
+                               else\r
+                                       range.setStart( topEnd.getParent(), endIndex );\r
                        }\r
 \r
                        // Collapse it to the start.\r
@@ -263,53 +347,70 @@ CKEDITOR.dom.range = function( document )
 \r
        // Creates the appropriate node evaluator for the dom walker used inside\r
        // check(Start|End)OfBlock.\r
-       function getCheckStartEndBlockEvalFunction( isStart )\r
+       function getCheckStartEndBlockEvalFunction()\r
        {\r
-               var hadBr = false, bookmarkEvaluator = CKEDITOR.dom.walker.bookmark( true );\r
+               var skipBogus = false,\r
+                       whitespaces = CKEDITOR.dom.walker.whitespaces(),\r
+                       bookmarkEvaluator = CKEDITOR.dom.walker.bookmark( true ),\r
+                       isBogus = CKEDITOR.dom.walker.bogus();\r
+\r
                return function( node )\r
                {\r
-                       // First ignore bookmark nodes.\r
-                       if ( bookmarkEvaluator( node ) )\r
+                       // First skip empty nodes.\r
+                       if ( bookmarkEvaluator( node ) || whitespaces( node ) )\r
                                return true;\r
 \r
-                       if ( node.type == CKEDITOR.NODE_TEXT )\r
-                       {\r
-                               // If there's any visible text, then we're not at the start.\r
-                               if ( CKEDITOR.tools.trim( node.getText() ).length )\r
-                                       return false;\r
-                               }\r
-                       else if ( node.type == CKEDITOR.NODE_ELEMENT )\r
+                       // Skip the bogus node at the end of block.\r
+                       if ( isBogus( node ) &&\r
+                                !skipBogus )\r
                        {\r
-                               // If there are non-empty inline elements (e.g. <img />), then we're not\r
-                               // at the start.\r
-                               if ( !inlineChildReqElements[ node.getName() ] )\r
-                               {\r
-                                       // If we're working at the end-of-block, forgive the first <br /> in non-IE\r
-                                       // browsers.\r
-                                       if ( !isStart && !CKEDITOR.env.ie && node.getName() == 'br' && !hadBr )\r
-                                               hadBr = true;\r
-                                       else\r
-                                               return false;\r
-                               }\r
+                               skipBogus = true;\r
+                               return true;\r
                        }\r
+\r
+                       // If there's any visible text, then we're not at the start.\r
+                       if ( node.type == CKEDITOR.NODE_TEXT &&\r
+                                        ( node.hasAscendant( 'pre' ) ||\r
+                                                CKEDITOR.tools.trim( node.getText() ).length ) )\r
+                               return false;\r
+\r
+                       // If there are non-empty inline elements (e.g. <img />), then we're not\r
+                       // at the start.\r
+                       if ( node.type == CKEDITOR.NODE_ELEMENT && !inlineChildReqElements[ node.getName() ] )\r
+                               return false;\r
+\r
                        return true;\r
                };\r
        }\r
 \r
+\r
+       var isBogus = CKEDITOR.dom.walker.bogus();\r
        // Evaluator for CKEDITOR.dom.element::checkBoundaryOfElement, reject any\r
        // text node and non-empty elements unless it's being bookmark text.\r
-       function elementBoundaryEval( node )\r
+       function elementBoundaryEval( checkStart )\r
        {\r
-               // Reject any text node unless it's being bookmark\r
-               // OR it's spaces. (#3883)\r
-               return node.type != CKEDITOR.NODE_TEXT\r
-                           && node.getName() in CKEDITOR.dtd.$removeEmpty\r
-                           || !CKEDITOR.tools.trim( node.getText() )\r
-                           || node.getParent().hasAttribute( '_fck_bookmark' );\r
+               var whitespaces = CKEDITOR.dom.walker.whitespaces(),\r
+                       bookmark = CKEDITOR.dom.walker.bookmark( 1 );\r
+\r
+               return function( node )\r
+               {\r
+                       // First skip empty nodes.\r
+                       if ( bookmark( node ) || whitespaces( node ) )\r
+                               return true;\r
+\r
+                       // Tolerant bogus br when checking at the end of block.\r
+                       // Reject any text node unless it's being bookmark\r
+                       // OR it's spaces.\r
+                       // Reject any element unless it's being invisible empty. (#3883)\r
+                       return !checkStart && isBogus( node ) ||\r
+                                                node.type == CKEDITOR.NODE_ELEMENT &&\r
+                                                node.getName() in CKEDITOR.dtd.$removeEmpty;\r
+               };\r
        }\r
 \r
        var whitespaceEval = new CKEDITOR.dom.walker.whitespaces(),\r
-               bookmarkEval = new CKEDITOR.dom.walker.bookmark();\r
+               bookmarkEval = new CKEDITOR.dom.walker.bookmark(),\r
+               nbspRegExp = /^[\t\r\n ]*(?:&nbsp;|\xa0)$/;\r
 \r
        function nonWhitespaceOrBookmarkEval( node )\r
        {\r
@@ -348,7 +449,10 @@ CKEDITOR.dom.range = function( document )
                        this.collapsed = true;\r
                },\r
 \r
-               // The selection may be lost when cloning (due to the splitText() call).\r
+               /**\r
+                *  The content nodes of the range are cloned and added to a document fragment, which is returned.\r
+                *  <strong> Note: </strong> Text selection may lost after invoking this method. (caused by text node splitting).\r
+                */\r
                cloneContents : function()\r
                {\r
                        var docFrag = new CKEDITOR.dom.documentFragment( this.document );\r
@@ -359,20 +463,29 @@ CKEDITOR.dom.range = function( document )
                        return docFrag;\r
                },\r
 \r
-               deleteContents : function()\r
+               /**\r
+                * Deletes the content nodes of the range permanently from the DOM tree.\r
+                * @param {Boolean} [mergeThen] Merge any splitted elements result in DOM true due to partial selection.\r
+                */\r
+               deleteContents : function( mergeThen )\r
                {\r
                        if ( this.collapsed )\r
                                return;\r
 \r
-                       execContentsAction( this, 0 );\r
+                       execContentsAction( this, 0, null, mergeThen );\r
                },\r
 \r
-               extractContents : function()\r
+               /**\r
+                *  The content nodes of the range are cloned and added to a document fragment,\r
+                * meanwhile they're removed permanently from the DOM tree.\r
+                * @param {Boolean} [mergeThen] Merge any splitted elements result in DOM true due to partial selection.\r
+                */\r
+               extractContents : function( mergeThen )\r
                {\r
                        var docFrag = new CKEDITOR.dom.documentFragment( this.document );\r
 \r
                        if ( !this.collapsed )\r
-                               execContentsAction( this, 1, docFrag );\r
+                               execContentsAction( this, 1, docFrag, mergeThen );\r
 \r
                        return docFrag;\r
                },\r
@@ -397,9 +510,10 @@ CKEDITOR.dom.range = function( document )
                        var startNode, endNode;\r
                        var baseId;\r
                        var clone;\r
+                       var collapsed = this.collapsed;\r
 \r
                        startNode = this.document.createElement( 'span' );\r
-                       startNode.setAttribute( '_fck_bookmark', 1 );\r
+                       startNode.data( 'cke-bookmark', 1 );\r
                        startNode.setStyle( 'display', 'none' );\r
 \r
                        // For IE, it must have something inside, otherwise it may be\r
@@ -409,11 +523,11 @@ CKEDITOR.dom.range = function( document )
                        if ( serializable )\r
                        {\r
                                baseId = 'cke_bm_' + CKEDITOR.tools.getNextNumber();\r
-                               startNode.setAttribute( 'id', baseId + 'S' );\r
+                               startNode.setAttribute( 'id', baseId + ( collapsed ? 'C' : 'S' ) );\r
                        }\r
 \r
                        // If collapsed, the endNode will not be created.\r
-                       if ( !this.collapsed )\r
+                       if ( !collapsed )\r
                        {\r
                                endNode = startNode.clone();\r
                                endNode.setHtml( '&nbsp;' );\r
@@ -440,9 +554,10 @@ CKEDITOR.dom.range = function( document )
                                this.moveToPosition( startNode, CKEDITOR.POSITION_AFTER_END );\r
 \r
                        return {\r
-                               startNode : serializable ? baseId + 'S' : startNode,\r
+                               startNode : serializable ? baseId + ( collapsed ? 'C' : 'S' ) : startNode,\r
                                endNode : serializable ? baseId + 'E' : endNode,\r
-                               serializable : serializable\r
+                               serializable : serializable,\r
+                               collapsed : collapsed\r
                        };\r
                },\r
 \r
@@ -465,6 +580,8 @@ CKEDITOR.dom.range = function( document )
                        var startOffset = this.startOffset,\r
                                endOffset       = this.endOffset;\r
 \r
+                       var collapsed = this.collapsed;\r
+\r
                        var child, previous;\r
 \r
                        // If there is no range then get out of here.\r
@@ -489,6 +606,10 @@ CKEDITOR.dom.range = function( document )
                                                startContainer = child;\r
                                                startOffset = 0;\r
                                        }\r
+\r
+                                       // Get the normalized offset.\r
+                                       if ( child && child.type == CKEDITOR.NODE_ELEMENT )\r
+                                               startOffset = child.getIndex( 1 );\r
                                }\r
 \r
                                // Normalize the start.\r
@@ -501,7 +622,7 @@ CKEDITOR.dom.range = function( document )
                                }\r
 \r
                                // Process the end only if not normalized.\r
-                               if ( !this.isCollapsed )\r
+                               if ( !collapsed )\r
                                {\r
                                        // Find out if the start is pointing to a text node that\r
                                        // will be normalized.\r
@@ -517,6 +638,10 @@ CKEDITOR.dom.range = function( document )
                                                        endContainer = child;\r
                                                        endOffset = 0;\r
                                                }\r
+\r
+                                               // Get the normalized offset.\r
+                                               if ( child && child.type == CKEDITOR.NODE_ELEMENT )\r
+                                                       endOffset = child.getIndex( 1 );\r
                                        }\r
 \r
                                        // Normalize the end.\r
@@ -532,10 +657,11 @@ CKEDITOR.dom.range = function( document )
 \r
                        return {\r
                                start           : startContainer.getAddress( normalized ),\r
-                               end                     : this.isCollapsed ? null : endContainer.getAddress( normalized ),\r
+                               end                     : collapsed ? null : endContainer.getAddress( normalized ),\r
                                startOffset     : startOffset,\r
                                endOffset       : endOffset,\r
                                normalized      : normalized,\r
+                               collapsed       : collapsed,\r
                                is2                     : true          // It's a createBookmark2 bookmark.\r
                        };\r
                },\r
@@ -697,7 +823,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
@@ -705,10 +831,10 @@ CKEDITOR.dom.range = function( document )
                                endNode = this.endContainer;\r
 \r
                        if ( startNode.is && startNode.is( 'span' )\r
-                               && startNode.hasAttribute( '_fck_bookmark' ) )\r
+                               && startNode.data( 'cke-bookmark' ) )\r
                                this.setStartAt( startNode, CKEDITOR.POSITION_BEFORE_START );\r
                        if ( endNode && endNode.is && endNode.is( 'span' )\r
-                               && endNode.hasAttribute( '_fck_bookmark' ) )\r
+                               && endNode.data( 'cke-bookmark' ) )\r
                                this.setEndAt( endNode,  CKEDITOR.POSITION_AFTER_END );\r
                },\r
 \r
@@ -793,7 +919,12 @@ CKEDITOR.dom.range = function( document )
                        }\r
                },\r
 \r
-               enlarge : function( unit )\r
+               /**\r
+                * Expands the range so that partial units are completely contained.\r
+                * @param unit {Number} The unit type to expand with.\r
+                * @param {Boolean} [excludeBrs=false] Whether include line-breaks when expanding.\r
+                */\r
+               enlarge : function( unit, excludeBrs )\r
                {\r
                        switch ( unit )\r
                        {\r
@@ -900,7 +1031,12 @@ CKEDITOR.dom.range = function( document )
                                                        // whitespaces at the end.\r
                                                        isWhiteSpace = false;\r
 \r
-                                                       if ( sibling.type == CKEDITOR.NODE_TEXT )\r
+                                                       if ( sibling.type == CKEDITOR.NODE_COMMENT )\r
+                                                       {\r
+                                                               sibling = sibling.getPrevious();\r
+                                                               continue;\r
+                                                       }\r
+                                                       else if ( sibling.type == CKEDITOR.NODE_TEXT )\r
                                                        {\r
                                                                siblingText = sibling.getText();\r
 \r
@@ -914,7 +1050,8 @@ CKEDITOR.dom.range = function( document )
                                                                // If this is a visible element.\r
                                                                // We need to check for the bookmark attribute because IE insists on\r
                                                                // rendering the display:none nodes we use for bookmarks. (#3363)\r
-                                                               if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) )\r
+                                                               // Line-breaks (br) are rendered with zero width, which we don't want to include. (#7041)\r
+                                                               if ( ( sibling.$.offsetWidth > 0 || excludeBrs && sibling.is( 'br' ) ) && !sibling.data( 'cke-bookmark' ) )\r
                                                                {\r
                                                                        // We'll accept it only if we need\r
                                                                        // whitespace, and this is an inline\r
@@ -929,7 +1066,7 @@ CKEDITOR.dom.range = function( document )
                                                                                        sibling = null;\r
                                                                                else\r
                                                                                {\r
-                                                                                       var allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );\r
+                                                                                       var allChildren = sibling.$.getElementsByTagName( '*' );\r
                                                                                        for ( var i = 0, child ; child = allChildren[ i++ ] ; )\r
                                                                                        {\r
                                                                                                if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )\r
@@ -1068,12 +1205,13 @@ CKEDITOR.dom.range = function( document )
 \r
                                                                isWhiteSpace = /^[\s\ufeff]/.test( siblingText );\r
                                                        }\r
-                                                       else\r
+                                                       else if ( sibling.type == CKEDITOR.NODE_ELEMENT )\r
                                                        {\r
                                                                // If this is a visible element.\r
                                                                // We need to check for the bookmark attribute because IE insists on\r
                                                                // rendering the display:none nodes we use for bookmarks. (#3363)\r
-                                                               if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) )\r
+                                                               // Line-breaks (br) are rendered with zero width, which we don't want to include. (#7041)\r
+                                                               if ( ( sibling.$.offsetWidth > 0 || excludeBrs && sibling.is( 'br' ) ) && !sibling.data( 'cke-bookmark' ) )\r
                                                                {\r
                                                                        // We'll accept it only if we need\r
                                                                        // whitespace, and this is an inline\r
@@ -1088,7 +1226,7 @@ CKEDITOR.dom.range = function( document )
                                                                                        sibling = null;\r
                                                                                else\r
                                                                                {\r
-                                                                                       allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );\r
+                                                                                       allChildren = sibling.$.getElementsByTagName( '*' );\r
                                                                                        for ( i = 0 ; child = allChildren[ i++ ] ; )\r
                                                                                        {\r
                                                                                                if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )\r
@@ -1106,6 +1244,8 @@ CKEDITOR.dom.range = function( document )
                                                                                sibling = null;\r
                                                                }\r
                                                        }\r
+                                                       else\r
+                                                               isWhiteSpace = 1;\r
 \r
                                                        if ( isWhiteSpace )\r
                                                        {\r
@@ -1166,13 +1306,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 +1333,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
@@ -1203,6 +1344,22 @@ CKEDITOR.dom.range = function( document )
                                                                CKEDITOR.POSITION_AFTER_START :\r
                                                                CKEDITOR.POSITION_AFTER_END );\r
 \r
+                                       // Avoid enlarging the range further when end boundary spans right after the BR. (#7490)\r
+                                       if ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS )\r
+                                       {\r
+                                               var theRange = this.clone();\r
+                                               walker = new CKEDITOR.dom.walker( theRange );\r
+\r
+                                               var whitespaces = CKEDITOR.dom.walker.whitespaces(),\r
+                                                       bookmark = CKEDITOR.dom.walker.bookmark();\r
+\r
+                                               walker.evaluator = function( node ) { return !whitespaces( node ) && !bookmark( node ); };\r
+                                               var previous = walker.previous();\r
+                                               if ( previous && previous.type == CKEDITOR.NODE_ELEMENT && previous.is( 'br' ) )\r
+                                                       return;\r
+                                       }\r
+\r
+\r
                                        // Enlarging the end boundary.\r
                                        walkerRange = this.clone();\r
                                        walkerRange.collapse();\r
@@ -1220,8 +1377,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
@@ -1237,8 +1394,15 @@ CKEDITOR.dom.range = function( document )
 \r
                /**\r
                 *  Descrease the range to make sure that boundaries\r
-                *  always anchor beside text nodes or innermost element.\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
@@ -1287,7 +1451,8 @@ CKEDITOR.dom.range = function( document )
                                        }\r
                                }\r
 \r
-                               var walker = new CKEDITOR.dom.walker( walkerRange );\r
+                               var walker = new CKEDITOR.dom.walker( walkerRange ),\r
+                                       isBookmark = CKEDITOR.dom.walker.bookmark();\r
 \r
                                walker.evaluator = function( node )\r
                                {\r
@@ -1298,6 +1463,9 @@ CKEDITOR.dom.range = function( document )
                                var currentElement;\r
                                walker.guard = function( node, movingOut )\r
                                {\r
+                                       if ( isBookmark( node ) )\r
+                                               return true;\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
@@ -1385,7 +1553,7 @@ CKEDITOR.dom.range = function( document )
                        // 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
+                               startOffset = startNode.getIndex(), startNode = startNode.getParent();\r
 \r
                        this.startContainer     = startNode;\r
                        this.startOffset        = startOffset;\r
@@ -1416,7 +1584,7 @@ CKEDITOR.dom.range = function( document )
                        // 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
+                               endOffset = endNode.getIndex() + 1, endNode = endNode.getParent();\r
 \r
                        this.endContainer       = endNode;\r
                        this.endOffset          = endOffset;\r
@@ -1624,26 +1792,36 @@ CKEDITOR.dom.range = function( document )
                },\r
 \r
                /**\r
-                * Check whether current range is on the inner edge of the specified element.\r
-                * @param {Number} checkType ( CKEDITOR.START | CKEDITOR.END ) The checking side.\r
+                * Check whether a range boundary is at the inner boundary of a given\r
+                * element.\r
                 * @param {CKEDITOR.dom.element} element The target element to check.\r
+                * @param {Number} checkType The boundary to check for both the range\r
+                *              and the element. It can be CKEDITOR.START or CKEDITOR.END.\r
+                * @returns {Boolean} "true" if the range boundary is at the inner\r
+                *              boundary of the element.\r
                 */\r
                checkBoundaryOfElement : function( element, checkType )\r
                {\r
+                       var checkStart = ( checkType == CKEDITOR.START );\r
+\r
+                       // Create a copy of this range, so we can manipulate it for our checks.\r
                        var walkerRange = this.clone();\r
+\r
+                       // Collapse the range at the proper size.\r
+                       walkerRange.collapse( checkStart );\r
+\r
                        // Expand the range to element boundary.\r
-                       walkerRange[ checkType == CKEDITOR.START ?\r
-                        'setStartAt' : 'setEndAt' ]\r
-                        ( element, checkType == CKEDITOR.START ?\r
-                          CKEDITOR.POSITION_AFTER_START\r
-                          : CKEDITOR.POSITION_BEFORE_END );\r
+                       walkerRange[ checkStart ? 'setStartAt' : 'setEndAt' ]\r
+                        ( element, checkStart ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_END );\r
 \r
-                       var walker = new CKEDITOR.dom.walker( walkerRange ),\r
-                        retval = false;\r
-                       walker.evaluator = elementBoundaryEval;\r
-                       return walker[ checkType == CKEDITOR.START ?\r
-                               'checkBackward' : 'checkForward' ]();\r
+                       // Create the walker, which will check if we have anything useful\r
+                       // in the range.\r
+                       var walker = new CKEDITOR.dom.walker( walkerRange );\r
+                       walker.evaluator = elementBoundaryEval( checkStart );\r
+\r
+                       return walker[ checkStart ? '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
@@ -1651,20 +1829,15 @@ CKEDITOR.dom.range = function( document )
                        var startContainer = this.startContainer,\r
                                startOffset = this.startOffset;\r
 \r
-                       // If the starting node is a text node, and non-empty before the offset,\r
-                       // then we're surely not at the start of block.\r
-                       if ( startOffset && startContainer.type == CKEDITOR.NODE_TEXT )\r
+                       // [IE] Special handling for range start in text with a leading NBSP,\r
+                       // we it to be isolated, for bogus check.\r
+                       if ( CKEDITOR.env.ie && startOffset && startContainer.type == CKEDITOR.NODE_TEXT )\r
                        {\r
                                var textBefore = CKEDITOR.tools.ltrim( startContainer.substring( 0, startOffset ) );\r
-                               if ( textBefore.length )\r
-                                       return false;\r
+                               if ( nbspRegExp.test( textBefore ) )\r
+                                       this.trim( 0, 1 );\r
                        }\r
 \r
-                       // Antecipate the trim() call here, so the walker will not make\r
-                       // changes to the DOM, which would not get reflected into this\r
-                       // range otherwise.\r
-                       this.trim();\r
-\r
                        // We need to grab the block element holding the start boundary, so\r
                        // let's use an element path for it.\r
                        var path = new CKEDITOR.dom.elementPath( this.startContainer );\r
@@ -1675,7 +1848,7 @@ CKEDITOR.dom.range = function( document )
                        walkerRange.setStartAt( path.block || path.blockLimit, CKEDITOR.POSITION_AFTER_START );\r
 \r
                        var walker = new CKEDITOR.dom.walker( walkerRange );\r
-                       walker.evaluator = getCheckStartEndBlockEvalFunction( true );\r
+                       walker.evaluator = getCheckStartEndBlockEvalFunction();\r
 \r
                        return walker.checkBackward();\r
                },\r
@@ -1685,20 +1858,15 @@ CKEDITOR.dom.range = function( document )
                        var endContainer = this.endContainer,\r
                                endOffset = this.endOffset;\r
 \r
-                       // If the ending node is a text node, and non-empty after the offset,\r
-                       // then we're surely not at the end of block.\r
-                       if ( endContainer.type == CKEDITOR.NODE_TEXT )\r
+                       // [IE] Special handling for range end in text with a following NBSP,\r
+                       // we it to be isolated, for bogus check.\r
+                       if ( CKEDITOR.env.ie && endContainer.type == CKEDITOR.NODE_TEXT )\r
                        {\r
                                var textAfter = CKEDITOR.tools.rtrim( endContainer.substring( endOffset ) );\r
-                               if ( textAfter.length )\r
-                                       return false;\r
+                               if ( nbspRegExp.test( textAfter ) )\r
+                                       this.trim( 1, 0 );\r
                        }\r
 \r
-                       // Antecipate the trim() call here, so the walker will not make\r
-                       // changes to the DOM, which would not get reflected into this\r
-                       // range otherwise.\r
-                       this.trim();\r
-\r
                        // We need to grab the block element holding the start boundary, so\r
                        // let's use an element path for it.\r
                        var path = new CKEDITOR.dom.elementPath( this.endContainer );\r
@@ -1709,12 +1877,91 @@ CKEDITOR.dom.range = function( document )
                        walkerRange.setEndAt( path.block || path.blockLimit, CKEDITOR.POSITION_BEFORE_END );\r
 \r
                        var walker = new CKEDITOR.dom.walker( walkerRange );\r
-                       walker.evaluator = getCheckStartEndBlockEvalFunction( false );\r
+                       walker.evaluator = getCheckStartEndBlockEvalFunction();\r
 \r
                        return walker.checkForward();\r
                },\r
 \r
                /**\r
+                * Traverse with {@link CKEDITOR.dom.walker} to retrieve the previous element before the range start.\r
+                * @param {Function} evaluator Function used as the walker's evaluator.\r
+                * @param {Function} [guard] Function used as the walker's guard.\r
+                * @param {CKEDITOR.dom.element} [boundary] A range ancestor element in which the traversal is limited,\r
+                * default to the root editable if not defined.\r
+                *\r
+                * @return {CKEDITOR.dom.element|null} The returned node from the traversal.\r
+                */\r
+               getPreviousNode : function( evaluator, guard, boundary ) {\r
+\r
+                       var walkerRange = this.clone();\r
+                       walkerRange.collapse( 1 );\r
+                       walkerRange.setStartAt( boundary || this.document.getBody(), CKEDITOR.POSITION_AFTER_START );\r
+\r
+                       var walker = new CKEDITOR.dom.walker( walkerRange );\r
+                       walker.evaluator = evaluator;\r
+                       walker.guard = guard;\r
+                       return walker.previous();\r
+               },\r
+\r
+               /**\r
+                * Traverse with {@link CKEDITOR.dom.walker} to retrieve the next element before the range start.\r
+                * @param {Function} evaluator Function used as the walker's evaluator.\r
+                * @param {Function} [guard] Function used as the walker's guard.\r
+                * @param {CKEDITOR.dom.element} [boundary] A range ancestor element in which the traversal is limited,\r
+                * default to the root editable if not defined.\r
+                *\r
+                * @return {CKEDITOR.dom.element|null} The returned node from the traversal.\r
+                */\r
+               getNextNode: function( evaluator, guard, boundary )\r
+               {\r
+                       var walkerRange = this.clone();\r
+                       walkerRange.collapse();\r
+                       walkerRange.setEndAt( boundary || this.document.getBody(), CKEDITOR.POSITION_BEFORE_END );\r
+\r
+                       var walker = new CKEDITOR.dom.walker( walkerRange );\r
+                       walker.evaluator = evaluator;\r
+                       walker.guard = guard;\r
+                       return walker.next();\r
+               },\r
+\r
+               checkReadOnly : ( function()\r
+               {\r
+                       function checkNodesEditable( node, anotherEnd )\r
+                       {\r
+                               while( node )\r
+                               {\r
+                                       if ( node.type == CKEDITOR.NODE_ELEMENT )\r
+                                       {\r
+                                               if ( node.getAttribute( 'contentEditable' ) == 'false'\r
+                                                       && !node.data( 'cke-editable' ) )\r
+                                               {\r
+                                                       return 0;\r
+                                               }\r
+                                               // Range enclosed entirely in an editable element.\r
+                                               else if ( node.is( 'html' )\r
+                                                       || node.getAttribute( 'contentEditable' ) == 'true'\r
+                                                       && ( node.contains( anotherEnd ) || node.equals( anotherEnd ) ) )\r
+                                               {\r
+                                                       break;\r
+                                               }\r
+                                       }\r
+                                       node = node.getParent();\r
+                               }\r
+\r
+                               return 1;\r
+                       }\r
+\r
+                       return function()\r
+                       {\r
+                               var startNode = this.startContainer,\r
+                                       endNode = this.endContainer;\r
+\r
+                               // Check if elements path at both boundaries are editable.\r
+                               return !( checkNodesEditable( startNode, endNode ) && checkNodesEditable( endNode, startNode ) );\r
+                       };\r
+               })(),\r
+\r
+               /**\r
                 * Moves the range boundaries to the first/end editing point inside an\r
                 * element. For example, in an element tree like\r
                 * "&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
@@ -1725,47 +1972,65 @@ CKEDITOR.dom.range = function( document )
                 */\r
                moveToElementEditablePosition : function( el, isMoveToEnd )\r
                {\r
-                       var isEditable;\r
+                       function nextDFS( node, childOnly )\r
+                       {\r
+                               var next;\r
 \r
-                       // Empty elements are rejected.\r
-                       if ( CKEDITOR.dtd.$empty[ el.getName() ] )\r
-                               return false;\r
+                               if ( node.type == CKEDITOR.NODE_ELEMENT && node.isEditable( false ) )\r
+                                       next = node[ isMoveToEnd ? 'getLast' : 'getFirst' ]( nonWhitespaceOrBookmarkEval );\r
+\r
+                               if ( !childOnly && !next )\r
+                                       next = node[ isMoveToEnd ? 'getPrevious' : 'getNext' ]( nonWhitespaceOrBookmarkEval );\r
 \r
-                       while ( el && el.type == CKEDITOR.NODE_ELEMENT )\r
+                               return next;\r
+                       }\r
+\r
+                       // Handle non-editable element e.g. HR.\r
+                       if ( el.type == CKEDITOR.NODE_ELEMENT && !el.isEditable( false ) )\r
                        {\r
-                               isEditable = el.isEditable();\r
-\r
-                               // If an editable element is found, move inside it.\r
-                               if ( isEditable )\r
-                                       this.moveToPosition( el, isMoveToEnd ?\r
-                                                                CKEDITOR.POSITION_BEFORE_END :\r
-                                                                CKEDITOR.POSITION_AFTER_START );\r
-                               // Stop immediately if we've found a non editable inline element (e.g <img>).\r
-                               else if ( CKEDITOR.dtd.$inline[ el.getName() ] )\r
-                               {\r
-                                       this.moveToPosition( el, isMoveToEnd ?\r
-                                                                CKEDITOR.POSITION_AFTER_END :\r
-                                                                CKEDITOR.POSITION_BEFORE_START );\r
-                                       return true;\r
-                               }\r
+                               this.moveToPosition( el, isMoveToEnd ?\r
+                                                                                CKEDITOR.POSITION_AFTER_END :\r
+                                                                                CKEDITOR.POSITION_BEFORE_START );\r
+                               return true;\r
+                       }\r
 \r
-                               // Non-editable non-inline elements are to be bypassed, getting the next one.\r
-                               if ( CKEDITOR.dtd.$empty[ el.getName() ] )\r
-                                       el = el[ isMoveToEnd ? 'getPrevious' : 'getNext' ]( nonWhitespaceOrBookmarkEval );\r
-                               else\r
-                                       el = el[ isMoveToEnd ? 'getLast' : 'getFirst' ]( nonWhitespaceOrBookmarkEval );\r
+                       var found = 0;\r
 \r
+                       while ( el )\r
+                       {\r
                                // Stop immediately if we've found a text node.\r
-                               if ( el && el.type == CKEDITOR.NODE_TEXT )\r
+                               if ( el.type == CKEDITOR.NODE_TEXT )\r
                                {\r
-                                       this.moveToPosition( el, isMoveToEnd ?\r
+                                       // Put cursor before block filler.\r
+                                       if ( isMoveToEnd && this.checkEndOfBlock() && nbspRegExp.test( el.getText() ) )\r
+                                               this.moveToPosition( el, CKEDITOR.POSITION_BEFORE_START );\r
+                                       else\r
+                                               this.moveToPosition( el, isMoveToEnd ?\r
                                                                 CKEDITOR.POSITION_AFTER_END :\r
                                                                 CKEDITOR.POSITION_BEFORE_START );\r
-                                       return true;\r
+                                       found = 1;\r
+                                       break;\r
                                }\r
+\r
+                               // If an editable element is found, move inside it, but not stop the searching.\r
+                               if ( el.type == CKEDITOR.NODE_ELEMENT )\r
+                               {\r
+                                       if ( el.isEditable() )\r
+                                       {\r
+                                               this.moveToPosition( el, isMoveToEnd ?\r
+                                                                                                CKEDITOR.POSITION_BEFORE_END :\r
+                                                                                                CKEDITOR.POSITION_AFTER_START );\r
+                                               found = 1;\r
+                                       }\r
+                                       // Put cursor before padding block br.\r
+                                       else if ( isMoveToEnd && el.is( 'br' ) && this.checkEndOfBlock() )\r
+                                               this.moveToPosition( el, CKEDITOR.POSITION_BEFORE_START );\r
+                               }\r
+\r
+                               el = nextDFS( el, found );\r
                        }\r
 \r
-                       return isEditable;\r
+                       return !!found;\r
                },\r
 \r
                /**\r
@@ -1791,7 +2056,7 @@ CKEDITOR.dom.range = function( document )
                {\r
                        var walkerRange = this.clone();\r
 \r
-                       // Optimize and analyze the range to avoid DOM destructive nature of walker. (#\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
@@ -1841,13 +2106,13 @@ CKEDITOR.ENLARGE_ELEMENT = 1;
 CKEDITOR.ENLARGE_BLOCK_CONTENTS = 2;\r
 CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3;\r
 \r
-/**\r
- * Check boundary types.\r
- * @see CKEDITOR.dom.range::checkBoundaryOfElement\r
- */\r
+// Check boundary types.\r
+// @see CKEDITOR.dom.range.prototype.checkBoundaryOfElement\r
 CKEDITOR.START = 1;\r
 CKEDITOR.END = 2;\r
 CKEDITOR.STARTEND = 3;\r
 \r
+// Shrink range types.\r
+// @see CKEDITOR.dom.range.prototype.shrink\r
 CKEDITOR.SHRINK_ELEMENT = 1;\r
 CKEDITOR.SHRINK_TEXT = 2;\r