X-Git-Url: https://jasonwoof.com/gitweb/?a=blobdiff_plain;ds=sidebyside;f=_source%2Fcore%2Fdom%2Frange.js;h=b43a82dbf08b520e0b23d271b2cab1dd355b0224;hb=refs%2Ftags%2Fv3.4.2;hp=8d30b4fe2bc7ff4afbba7347c3b0899a16cebb63;hpb=c6e377a02b54abc07129d72b632763c727476a15;p=ckeditor.git
diff --git a/_source/core/dom/range.js b/_source/core/dom/range.js
index 8d30b4f..b43a82d 100644
--- a/_source/core/dom/range.js
+++ b/_source/core/dom/range.js
@@ -3,6 +3,9 @@ Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
For licensing, see LICENSE.html or http://ckeditor.com/license
*/
+/**
+ * @class
+ */
CKEDITOR.dom.range = function( document )
{
this.startContainer = null;
@@ -305,7 +308,7 @@ CKEDITOR.dom.range = function( document )
return node.type != CKEDITOR.NODE_TEXT
&& node.getName() in CKEDITOR.dtd.$removeEmpty
|| !CKEDITOR.tools.trim( node.getText() )
- || node.getParent().hasAttribute( '_fck_bookmark' );
+ || node.getParent().hasAttribute( '_cke_bookmark' );
}
var whitespaceEval = new CKEDITOR.dom.walker.whitespaces(),
@@ -397,9 +400,10 @@ CKEDITOR.dom.range = function( document )
var startNode, endNode;
var baseId;
var clone;
+ var collapsed = this.collapsed;
startNode = this.document.createElement( 'span' );
- startNode.setAttribute( '_fck_bookmark', 1 );
+ startNode.setAttribute( '_cke_bookmark', 1 );
startNode.setStyle( 'display', 'none' );
// For IE, it must have something inside, otherwise it may be
@@ -413,7 +417,7 @@ CKEDITOR.dom.range = function( document )
}
// If collapsed, the endNode will not be created.
- if ( !this.collapsed )
+ if ( !collapsed )
{
endNode = startNode.clone();
endNode.setHtml( ' ' );
@@ -442,7 +446,8 @@ CKEDITOR.dom.range = function( document )
return {
startNode : serializable ? baseId + 'S' : startNode,
endNode : serializable ? baseId + 'E' : endNode,
- serializable : serializable
+ serializable : serializable,
+ collapsed : collapsed
};
},
@@ -465,6 +470,8 @@ CKEDITOR.dom.range = function( document )
var startOffset = this.startOffset,
endOffset = this.endOffset;
+ var collapsed = this.collapsed;
+
var child, previous;
// If there is no range then get out of here.
@@ -501,7 +508,7 @@ CKEDITOR.dom.range = function( document )
}
// Process the end only if not normalized.
- if ( !this.isCollapsed )
+ if ( !collapsed )
{
// Find out if the start is pointing to a text node that
// will be normalized.
@@ -532,10 +539,11 @@ CKEDITOR.dom.range = function( document )
return {
start : startContainer.getAddress( normalized ),
- end : this.isCollapsed ? null : endContainer.getAddress( normalized ),
+ end : collapsed ? null : endContainer.getAddress( normalized ),
startOffset : startOffset,
endOffset : endOffset,
normalized : normalized,
+ collapsed : collapsed,
is2 : true // It's a createBookmark2 bookmark.
};
},
@@ -697,7 +705,7 @@ CKEDITOR.dom.range = function( document )
},
/**
- * Move the range out of bookmark nodes if they're been the container.
+ * Move the range out of bookmark nodes if they'd been the container.
*/
optimizeBookmark: function()
{
@@ -705,10 +713,10 @@ CKEDITOR.dom.range = function( document )
endNode = this.endContainer;
if ( startNode.is && startNode.is( 'span' )
- && startNode.hasAttribute( '_fck_bookmark' ) )
+ && startNode.hasAttribute( '_cke_bookmark' ) )
this.setStartAt( startNode, CKEDITOR.POSITION_BEFORE_START );
if ( endNode && endNode.is && endNode.is( 'span' )
- && endNode.hasAttribute( '_fck_bookmark' ) )
+ && endNode.hasAttribute( '_cke_bookmark' ) )
this.setEndAt( endNode, CKEDITOR.POSITION_AFTER_END );
},
@@ -914,7 +922,7 @@ CKEDITOR.dom.range = function( document )
// If this is a visible element.
// We need to check for the bookmark attribute because IE insists on
// rendering the display:none nodes we use for bookmarks. (#3363)
- if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) )
+ if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_cke_bookmark' ) )
{
// We'll accept it only if we need
// whitespace, and this is an inline
@@ -925,7 +933,7 @@ CKEDITOR.dom.range = function( document )
siblingText = sibling.getText();
- if ( !(/[^\s\ufeff]/).test( siblingText ) ) // Spaces + Zero Width No-Break Space (U+FEFF)
+ if ( (/[^\s\ufeff]/).test( siblingText ) ) // Spaces + Zero Width No-Break Space (U+FEFF)
sibling = null;
else
{
@@ -1073,7 +1081,7 @@ CKEDITOR.dom.range = function( document )
// If this is a visible element.
// We need to check for the bookmark attribute because IE insists on
// rendering the display:none nodes we use for bookmarks. (#3363)
- if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) )
+ if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_cke_bookmark' ) )
{
// We'll accept it only if we need
// whitespace, and this is an inline
@@ -1084,7 +1092,7 @@ CKEDITOR.dom.range = function( document )
siblingText = sibling.getText();
- if ( !(/[^\s\ufeff]/).test( siblingText ) )
+ if ( (/[^\s\ufeff]/).test( siblingText ) )
sibling = null;
else
{
@@ -1166,13 +1174,13 @@ CKEDITOR.dom.range = function( document )
var walker = new CKEDITOR.dom.walker( walkerRange ),
blockBoundary, // The node on which the enlarging should stop.
- tailBr, //
- defaultGuard = CKEDITOR.dom.walker.blockBoundary(
+ tailBr, // In case BR as block boundary.
+ notBlockBoundary = CKEDITOR.dom.walker.blockBoundary(
( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ? { br : 1 } : null ),
// Record the encountered 'blockBoundary' for later use.
boundaryGuard = function( node )
{
- var retval = defaultGuard( node );
+ var retval = notBlockBoundary( node );
if ( !retval )
blockBoundary = node;
return retval;
@@ -1193,8 +1201,9 @@ CKEDITOR.dom.range = function( document )
// It's the body which stop the enlarging if no block boundary found.
blockBoundary = blockBoundary || body;
- // Start the range at different position by comparing
- // the document position of it with 'enlargeable' node.
+ // Start the range either after the end of found block (
...
[text)
+ // or at the start of block ([text...), by comparing the document position
+ // with 'enlargeable' node.
this.setStartAt(
blockBoundary,
!blockBoundary.is( 'br' ) &&
@@ -1220,8 +1229,8 @@ CKEDITOR.dom.range = function( document )
// It's the body which stop the enlarging if no block boundary found.
blockBoundary = blockBoundary || body;
- // Start the range at different position by comparing
- // the document position of it with 'enlargeable' node.
+ // Close the range either before the found block start (text]
...
) or at the block end (...text])
+ // by comparing the document position with 'enlargeable' node.
this.setEndAt(
blockBoundary,
( !enlargeable && this.checkEndOfBlock()
@@ -1236,6 +1245,111 @@ CKEDITOR.dom.range = function( document )
},
/**
+ * Descrease the range to make sure that boundaries
+ * always anchor beside text nodes or innermost element.
+ * @param {Number} mode ( CKEDITOR.SHRINK_ELEMENT | CKEDITOR.SHRINK_TEXT ) The shrinking mode.
+ *
+ * - CKEDITOR.SHRINK_ELEMENT
+ * - Shrink the range boundaries to the edge of the innermost element.
+ * - CKEDITOR.SHRINK_TEXT
+ * - 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.
+ *
+ * @param {Boolean} selectContents Whether result range anchors at the inner OR outer boundary of the node.
+ */
+ shrink : function( mode, selectContents )
+ {
+ // Unable to shrink a collapsed range.
+ if ( !this.collapsed )
+ {
+ mode = mode || CKEDITOR.SHRINK_TEXT;
+
+ var walkerRange = this.clone();
+
+ var startContainer = this.startContainer,
+ endContainer = this.endContainer,
+ startOffset = this.startOffset,
+ endOffset = this.endOffset,
+ collapsed = this.collapsed;
+
+ // Whether the start/end boundary is moveable.
+ var moveStart = 1,
+ moveEnd = 1;
+
+ if ( startContainer && startContainer.type == CKEDITOR.NODE_TEXT )
+ {
+ if ( !startOffset )
+ walkerRange.setStartBefore( startContainer );
+ else if ( startOffset >= startContainer.getLength( ) )
+ walkerRange.setStartAfter( startContainer );
+ else
+ {
+ // Enlarge the range properly to avoid walker making
+ // DOM changes caused by triming the text nodes later.
+ walkerRange.setStartBefore( startContainer );
+ moveStart = 0;
+ }
+ }
+
+ if ( endContainer && endContainer.type == CKEDITOR.NODE_TEXT )
+ {
+ if ( !endOffset )
+ walkerRange.setEndBefore( endContainer );
+ else if ( endOffset >= endContainer.getLength( ) )
+ walkerRange.setEndAfter( endContainer );
+ else
+ {
+ walkerRange.setEndAfter( endContainer );
+ moveEnd = 0;
+ }
+ }
+
+ var walker = new CKEDITOR.dom.walker( walkerRange ),
+ isBookmark = CKEDITOR.dom.walker.bookmark();
+
+ walker.evaluator = function( node )
+ {
+ return node.type == ( mode == CKEDITOR.SHRINK_ELEMENT ?
+ CKEDITOR.NODE_ELEMENT : CKEDITOR.NODE_TEXT );
+ };
+
+ var currentElement;
+ walker.guard = function( node, movingOut )
+ {
+ if ( isBookmark( node ) )
+ return true;
+
+ // Stop when we're shrink in element mode while encountering a text node.
+ if ( mode == CKEDITOR.SHRINK_ELEMENT && node.type == CKEDITOR.NODE_TEXT )
+ return false;
+
+ // Stop when we've already walked "through" an element.
+ if ( movingOut && node.equals( currentElement ) )
+ return false;
+
+ if ( !movingOut && node.type == CKEDITOR.NODE_ELEMENT )
+ currentElement = node;
+
+ return true;
+ };
+
+ if ( moveStart )
+ {
+ var textStart = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastForward' : 'next']();
+ textStart && this.setStartAt( textStart, selectContents ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_START );
+ }
+
+ if ( moveEnd )
+ {
+ walker.reset();
+ var textEnd = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastBackward' : 'previous']();
+ textEnd && this.setEndAt( textEnd, selectContents ? CKEDITOR.POSITION_BEFORE_END : CKEDITOR.POSITION_AFTER_END );
+ }
+
+ return !!( moveStart || moveEnd );
+ }
+ },
+
+ /**
* Inserts a node at the start of the range. The range will be expanded
* the contain the node.
*/
@@ -1288,6 +1402,11 @@ CKEDITOR.dom.range = function( document )
// we will not need this check for our use of this class so we can
// ignore it for now.
+ // Fixing invalid range start inside dtd empty elements.
+ if( startNode.type == CKEDITOR.NODE_ELEMENT
+ && CKEDITOR.dtd.$empty[ startNode.getName() ] )
+ startNode = startNode.getParent(), startOffset = startNode.getIndex();
+
this.startContainer = startNode;
this.startOffset = startOffset;
@@ -1314,6 +1433,11 @@ CKEDITOR.dom.range = function( document )
// will not need this check for our use of this class so we can ignore
// it for now.
+ // Fixing invalid range end inside dtd empty elements.
+ if( endNode.type == CKEDITOR.NODE_ELEMENT
+ && CKEDITOR.dtd.$empty[ endNode.getName() ] )
+ endOffset = endNode.getIndex() + 1, endNode = endNode.getParent();
+
this.endContainer = endNode;
this.endOffset = endOffset;
@@ -1520,26 +1644,36 @@ CKEDITOR.dom.range = function( document )
},
/**
- * Check whether current range is on the inner edge of the specified element.
- * @param {Number} checkType ( CKEDITOR.START | CKEDITOR.END ) The checking side.
+ * Check whether a range boundary is at the inner boundary of a given
+ * element.
* @param {CKEDITOR.dom.element} element The target element to check.
+ * @param {Number} checkType The boundary to check for both the range
+ * and the element. It can be CKEDITOR.START or CKEDITOR.END.
+ * @returns {Boolean} "true" if the range boundary is at the inner
+ * boundary of the element.
*/
checkBoundaryOfElement : function( element, checkType )
{
+ var checkStart = ( checkType == CKEDITOR.START );
+
+ // Create a copy of this range, so we can manipulate it for our checks.
var walkerRange = this.clone();
+
+ // Collapse the range at the proper size.
+ walkerRange.collapse( checkStart );
+
// Expand the range to element boundary.
- walkerRange[ checkType == CKEDITOR.START ?
- 'setStartAt' : 'setEndAt' ]
- ( element, checkType == CKEDITOR.START ?
- CKEDITOR.POSITION_AFTER_START
- : CKEDITOR.POSITION_BEFORE_END );
+ walkerRange[ checkStart ? 'setStartAt' : 'setEndAt' ]
+ ( element, checkStart ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_END );
- var walker = new CKEDITOR.dom.walker( walkerRange ),
- retval = false;
+ // Create the walker, which will check if we have anything useful
+ // in the range.
+ var walker = new CKEDITOR.dom.walker( walkerRange );
walker.evaluator = elementBoundaryEval;
- return walker[ checkType == CKEDITOR.START ?
- 'checkBackward' : 'checkForward' ]();
+
+ return walker[ checkStart ? 'checkBackward' : 'checkForward' ]();
},
+
// Calls to this function may produce changes to the DOM. The range may
// be updated to reflect such changes.
checkStartOfBlock : function()
@@ -1623,6 +1757,10 @@ CKEDITOR.dom.range = function( document )
{
var isEditable;
+ // Empty elements are rejected.
+ if ( CKEDITOR.dtd.$empty[ el.getName() ] )
+ return false;
+
while ( el && el.type == CKEDITOR.NODE_ELEMENT )
{
isEditable = el.isEditable();
@@ -1681,8 +1819,15 @@ CKEDITOR.dom.range = function( document )
*/
getEnclosedNode : function()
{
- var walkerRange = this.clone(),
- walker = new CKEDITOR.dom.walker( walkerRange ),
+ var walkerRange = this.clone();
+
+ // Optimize and analyze the range to avoid DOM destructive nature of walker. (#5780)
+ walkerRange.optimize();
+ if ( walkerRange.startContainer.type != CKEDITOR.NODE_ELEMENT
+ || walkerRange.endContainer.type != CKEDITOR.NODE_ELEMENT )
+ return null;
+
+ var walker = new CKEDITOR.dom.walker( walkerRange ),
isNotBookmarks = CKEDITOR.dom.walker.bookmark( true ),
isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ),
evaluator = function( node )
@@ -1726,10 +1871,13 @@ CKEDITOR.ENLARGE_ELEMENT = 1;
CKEDITOR.ENLARGE_BLOCK_CONTENTS = 2;
CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3;
-/**
- * Check boundary types.
- * @see CKEDITOR.dom.range::checkBoundaryOfElement
- */
+// Check boundary types.
+// @see CKEDITOR.dom.range.prototype.checkBoundaryOfElement
CKEDITOR.START = 1;
CKEDITOR.END = 2;
CKEDITOR.STARTEND = 3;
+
+// Shrink range types.
+// @see CKEDITOR.dom.range.prototype.shrink
+CKEDITOR.SHRINK_ELEMENT = 1;
+CKEDITOR.SHRINK_TEXT = 2;