Index: /CKEditor/branches/prototype/_source/core/dom/document.js
===================================================================
--- /CKEditor/branches/prototype/_source/core/dom/document.js	(revision 2572)
+++ /CKEditor/branches/prototype/_source/core/dom/document.js	(revision 2573)
@@ -67,4 +67,25 @@
 				this.getHead().append( link );
 			}
+		},
+		
+		createElement : function( name, attribsAndStyles )
+		{
+			var element = new CKEDITOR.dom.element( name, this );
+			
+			if ( attribsAndStyles )
+			{
+				if ( attribsAndStyles.attributes )
+					element.setAttributes( attribsAndStyles.attributes );
+				
+				if ( attribsAndStyles.styles )
+					element.setStyles( attribsAndStyles.styles );
+			}
+			
+			return element;
+		},
+		
+		createText : function( text )
+		{
+			return new CKEDITOR.dom.text( '', this );
 		},
 
Index: /CKEditor/branches/prototype/_source/core/dom/documentFragment.js
===================================================================
--- /CKEditor/branches/prototype/_source/core/dom/documentFragment.js	(revision 2573)
+++ /CKEditor/branches/prototype/_source/core/dom/documentFragment.js	(revision 2573)
@@ -0,0 +1,45 @@
+﻿/*
+ * CKEditor - The text editor for Internet - http://ckeditor.com
+ * Copyright (C) 2003-2008 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ *  - GNU General Public License Version 2 or later (the "GPL")
+ *    http://www.gnu.org/licenses/gpl.html
+ *
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ *    http://www.gnu.org/licenses/lgpl.html
+ *
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")
+ *    http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ */
+
+CKEDITOR.dom.documentFragment = function( ownerDocument )
+{
+	this.$ = CKEDITOR.env.ie ? ownerDocument.$.createElement( 'div' ) : ownerDocument.$.createDocumentFragment();
+};
+
+(function()
+{
+	var elementPrototype = CKEDITOR.dom.element.prototype;
+
+	CKEDITOR.dom.documentFragment.prototype =
+	{
+		type : CKEDITOR.NODE_DOCUMENT_FRAGMENT,
+
+		append : elementPrototype.append,
+		
+		appendTo : function( targetElement )
+		{
+			if ( CKEDITOR.env.ie )
+				elementPrototype.moveChildren.call( this, targetElement );
+			else
+				targetElement.$.appendChild( this.$ );
+		}
+	};
+})();
Index: /CKEditor/branches/prototype/_source/core/dom/element.js
===================================================================
--- /CKEditor/branches/prototype/_source/core/dom/element.js	(revision 2572)
+++ /CKEditor/branches/prototype/_source/core/dom/element.js	(revision 2573)
@@ -94,4 +94,12 @@
 	{
 		/**
+		 * The node type. This is a constant value set to
+		 * {@link CKEDITOR.NODE_ELEMENT}.
+		 * @type Number
+		 * @example
+		 */
+		type : CKEDITOR.NODE_ELEMENT,
+
+		/**
 		 * Adds a CSS class to the element. It appends the class to the
 		 * already existing names.
@@ -187,4 +195,20 @@
 		},
 
+		contains :
+			CKEDITOR.env.ie || CKEDITOR.env.webkit ?
+				function( node )
+				{
+					var $ = this.$;
+
+					return node.type != CKEDITOR.NODE_ELEMENT ?
+						$.contains( node.getParent().$ ) :
+						$ != node.$ && $.contains( node.$ );
+				}
+			:
+				function( node )
+				{
+					return !!( this.$.compareDocumentPosition( node.$ ) & 16 );
+				},
+
 		/**
 		 * Moves the selection focus to this element.
@@ -336,4 +360,16 @@
 		},
 
+		getDtd : function()
+		{
+			var dtd = CKEDITOR.dtd[ this.getName() ];
+
+			this.getDtd = function()
+			{
+				return dtd;
+			};
+
+			return dtd;
+		},
+
 		/**
 		 * Gets the computed tabindex for this element.
@@ -386,4 +422,21 @@
 
 		/**
+		 * Gets the text value of this element.
+		 *
+		 * Only in IE (which uses innerText), &lt;br&gt; will cause linebreaks,
+		 * and sucessive whitespaces (including line breaks) will be reduced to
+		 * a single space. This behavior is ok for us, for now. It may change
+		 * in the future.
+		 * @returns {String} The text value.
+		 * @example
+		 * var element = CKEDITOR.dom.element.createFromHtml( '&lt;div&gt;Same &lt;i&gt;text&lt;/i&gt;.&lt;/div&gt;' );
+		 * alert( <b>element.getText()</b> );  // "Sample text."
+		 */
+		getText : function()
+		{
+			return this.$.textContent || this.$.innerText;
+		},
+
+		/**
 		 * Gets the window object that contains this element.
 		 * @returns {CKEDITOR.dom.window} The window object.
@@ -462,4 +515,10 @@
 		{
 			var $ = this.$.firstChild;
+			return $ ? new CKEDITOR.dom.node( $ ) : null;
+		},
+
+		getLast : function()
+		{
+			var $ = this.$.lastChild;
 			return $ ? new CKEDITOR.dom.node( $ ) : null;
 		},
@@ -511,4 +570,26 @@
 		{
 			this.setStyle( 'display', 'none' );
+		},
+
+		moveChildren : function( target, toStart )
+		{
+			var $ = this.$;
+			target = target.$;
+
+			if ( $ == target )
+				return;
+
+			var child;
+
+			if ( toStart )
+			{
+				while ( (child = $.lastChild) )
+					target.insertBefore( $.removeChild( child ), target.firstChild );
+			}
+			else
+			{
+				while ( (child = $.firstChild) )
+					target.appendChild( $.removeChild( child ) );
+			}
 		},
 
@@ -592,15 +673,4 @@
 			this.$.value = value;
 			return this;
-		},
-
-		/**
-		 * Removes the element from the document DOM.
-		 * @example
-		 * var element = CKEDITOR.dom.element.getById( 'MyElement' );
-		 * <b>element.remove()</b>;
-		 */
-		remove : function()
-		{
-			this.$.parentNode.removeChild( this.$ );
 		},
 
Index: /CKEditor/branches/prototype/_source/core/dom/node.js
===================================================================
--- /CKEditor/branches/prototype/_source/core/dom/node.js	(revision 2572)
+++ /CKEditor/branches/prototype/_source/core/dom/node.js	(revision 2573)
@@ -63,5 +63,5 @@
  * @example
  */
-CKEDITOR.NODE_ELEMENT	= 1;
+CKEDITOR.NODE_ELEMENT = 1;
 
 /**
@@ -70,5 +70,5 @@
  * @example
  */
-CKEDITOR.NODE_TEXT		= 3;
+CKEDITOR.NODE_TEXT = 3;
 
 /**
@@ -77,5 +77,7 @@
  * @example
  */
-CKEDITOR.NODE_COMMENT	= 8;
+CKEDITOR.NODE_COMMENT = 8;
+
+CKEDITOR.NODE_DOCUMENT_FRAGMENT = 11;
 
 CKEDITOR.tools.extend( CKEDITOR.dom.node.prototype,
@@ -100,16 +102,7 @@
 		},
 
-		/**
-		 * Gets the parent element for this node.
-		 * @returns {CKEDITOR.dom.element} The parent element.
-		 * @example
-		 * var node = editor.document.getBody().getFirst();
-		 * var parent = node.<b>getParent()</b>;
-		 * alert( node.getName() );  // "body"
-		 */
-		getParent : function()
-		{
-			var parent = this.$.parentNode;
-			return parent ? new CKEDITOR.dom.element( this.$.parentNode ) : null;
+		clone : function( includeChildren )
+		{
+			return new CKEDITOR.dom.node( this.$.cloneNode( includeChildren ) );
 		},
 
@@ -135,5 +128,5 @@
 		 * Inserts this element before a node.
 		 * @param {CKEDITOR.dom.node} node The that will be after this element.
-		 * @returns {CKEDITOR.dom.node} The node after this one after insertion.
+		 * @returns {CKEDITOR.dom.node} The node being inserted.
 		 * @example
 		 * var em = new CKEDITOR.dom.element( 'em' );
@@ -146,4 +139,11 @@
 		{
 			node.$.parentNode.insertBefore( this.$, node.$ );
+			return node;
+		},
+
+		insertBeforeMe : function( node )
+		{
+			this.$.parentNode.insertBefore( node.$, this.$ );
+			return node;
 		},
 
@@ -169,8 +169,83 @@
 			return rawNode ? new CKEDITOR.dom.node( rawNode ) : null;
 		},
-		
+
 		getChildCount : function()
 		{
 			return this.$.childNodes.length;
+		},
+
+		getIndex : function()
+		{
+			var $ = this.$;
+
+			var currentNode = $.parentNode && $.parentNode.firstChild;
+			var currentIndex = -1;
+
+			while ( currentNode )
+			{
+				currentIndex++;
+
+				if ( currentNode == $ )
+					return currentIndex;
+
+				currentNode = currentNode.nextSibling;
+			}
+
+			return -1;
+		},
+
+		/**
+		 * Gets the node following this node (next sibling).
+		 * @returns {CKEDITOR.dom.node} The next node.
+		 */
+		getNext : function()
+		{
+			var next = this.$.nextSibling;
+			return next ? new CKEDITOR.dom.node( next ) : null;
+		},
+
+		getPrevious : function()
+		{
+			var previous = this.$.previousSibling;
+			return previous ? new CKEDITOR.dom.node( previous ) : null;
+		},
+
+		/**
+		 * Gets the parent element for this node.
+		 * @returns {CKEDITOR.dom.element} The parent element.
+		 * @example
+		 * var node = editor.document.getBody().getFirst();
+		 * var parent = node.<b>getParent()</b>;
+		 * alert( node.getName() );  // "body"
+		 */
+		getParent : function()
+		{
+			var parent = this.$.parentNode;
+			return ( parent && parent.nodeType == 1 ) ? new CKEDITOR.dom.element( parent ) : null;
+		},
+
+		getParents : function()
+		{
+			var node = this;
+			var parents = [];
+
+			do
+			{
+				parents.unshift( node );
+			}
+			while ( ( node = node.getParent() ) )
+
+			return parents;
+		},
+
+		/**
+		 * Removes this node from the document DOM.
+		 * @example
+		 * var element = CKEDITOR.dom.element.getById( 'MyElement' );
+		 * <b>element.remove()</b>;
+		 */
+		remove : function()
+		{
+			this.$.parentNode.removeChild( this.$ );
 		}
 	}
Index: /CKEditor/branches/prototype/_source/core/dom/range.js
===================================================================
--- /CKEditor/branches/prototype/_source/core/dom/range.js	(revision 2573)
+++ /CKEditor/branches/prototype/_source/core/dom/range.js	(revision 2573)
@@ -0,0 +1,995 @@
+﻿/*
+ * CKEditor - The text editor for Internet - http://ckeditor.com
+ * Copyright (C) 2003-2008 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ *  - GNU General Public License Version 2 or later (the "GPL")
+ *    http://www.gnu.org/licenses/gpl.html
+ *
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ *    http://www.gnu.org/licenses/lgpl.html
+ *
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")
+ *    http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ */
+
+CKEDITOR.dom.range = function( document )
+{
+	this.startContainer	= null;
+	this.startOffset	= null;
+	this.endContainer	= null;
+	this.endOffset		= null;
+	this.collapsed		= true;
+
+	this.document = document;
+};
+
+(function()
+{
+	// Updates the "collapsed" property for the given range object.
+	var updateCollapsed = function( range )
+	{
+      range.collapsed = ( range.startContainer.equals( range.endContainer ) && range.startOffset == range.endOffset );
+	};
+
+	// This is a shared function used to delete, extract and clone the range
+	// contents.
+	// V2
+	var execContentsAction = function( range, action, docFrag )
+	{
+		var startNode	= range.startContainer;
+		var endNode		= range.endContainer;
+
+		var startOffset	= range.startOffset;
+		var endOffset	= range.endOffset;
+
+		var removeStartNode;
+		var removeEndNode;
+
+		// For text containers, we must simply split the node and point to the
+		// second part. The removal will be handled by the rest of the code .
+		if ( endNode.type == CKEDITOR.NODE_TEXT )
+			endNode = endNode.split( endOffset );
+		else
+		{
+			// If the end container has children and the offset is pointing
+			// to a child, then we should start from it.
+			if ( endNode.getChildCount() > 0 )
+			{
+				// If the offset points after the last node.
+				if ( endOffset >= endNode.getChildCount() )
+				{
+					// Let's create a temporary node and mark it for removal.
+					endNode = endNode.append( range.document.createText( '' ) );
+					removeEndNode = true;
+				}
+				else
+					endNode = endNode.getChild( endOffset );
+			}
+		}
+
+		// For text containers, we must simply split the node. The removal will
+		// be handled by the rest of the code .
+		if ( startNode.type == CKEDITOR.NODE_TEXT )
+		{
+			startNode.split( startOffset );
+
+			// In cases the end node is the same as the start node, the above
+			// splitting will also split the end, so me must move the end to
+			// the second part of the split.
+			if ( startNode.equals( endNode ) )
+				endNode = startNode.getNext();
+		}
+		else
+		{
+			// If the start container has children and the offset is pointing
+			// to a child, then we should start from its previous sibling.
+
+			// If the offset points to the first node, we don't have a
+			// sibling, so let's use the first one, but mark it for removal.
+			if ( !startOffset )
+			{
+				// Let's create a temporary node and mark it for removal.
+				startNode = startNode.getFirst().insertBeforeMe( range.document.createText( '' ) );
+				removeStartNode = true;
+			}
+			else if ( startOffset >= startNode.getChildCount() )
+			{
+				// Let's create a temporary node and mark it for removal.
+				startNode = startNode.append( range.document.createText( '' ) );
+				removeStartNode = true;
+			}
+			else
+				startNode = startNode.getChild( startOffset ).getPrevious();
+		}
+
+		// Get the parent nodes tree for the start and end boundaries.
+		var startParents	= startNode.getParents();
+		var endParents		= endNode.getParents();
+
+		// Compare them, to find the top most siblings.
+		var i, topStart, topEnd;
+
+		for ( i = 0 ; i < startParents.length ; i++ )
+		{
+			topStart = startParents[ i ];
+			topEnd = endParents[ i ];
+
+			// The compared nodes will match until we find the top most
+			// siblings (different nodes that have the same parent).
+			// "i" will hold the index in the parents array for the top
+			// most element.
+			if ( !topStart.equals( topEnd ) )
+				break;
+		}
+
+		var clone = docFrag, levelStartNode, levelClone, currentNode, currentSibling;
+
+		// Remove all successive sibling nodes for every node in the
+		// startParents tree.
+		for ( var j = i ; j < startParents.length ; j++ )
+		{
+			levelStartNode = startParents[j];
+
+			// For Extract and Clone, we must clone this level.
+			if ( clone && !levelStartNode.equals( startNode ) )		// action = 0 = Delete
+				levelClone = clone.append( levelStartNode.clone() );
+
+			currentNode = levelStartNode.getNext();
+
+			while( currentNode )
+			{
+				// Stop processing when the current node matches a node in the
+				// endParents tree or if it is the endNode.
+				if ( currentNode.equals( endParents[ j ] ) || currentNode.equals( endNode ) )
+					break;
+
+				// Cache the next sibling.
+				currentSibling = currentNode.getNext();
+
+				// If cloning, just clone it.
+				if ( action == 2 )	// 2 = Clone
+					clone.append( currentNode.clone( true ) );
+				else
+				{
+					// Both Delete and Extract will remove the node.
+					currentNode.remove();
+
+					// When Extracting, move the removed node to the docFrag.
+					if ( action == 1 )	// 1 = Extract
+						clone.append( currentNode );
+				}
+
+				currentNode = currentSibling;
+			}
+
+			if ( clone )
+				clone = levelClone;
+		}
+
+		clone = docFrag;
+
+		// Remove all previous sibling nodes for every node in the
+		// endParents tree.
+		for ( var k = i ; k < endParents.length ; k++ )
+		{
+			levelStartNode = endParents[ k ];
+
+			// For Extract and Clone, we must clone this level.
+			if ( action > 0 && !levelStartNode.equals( endNode ) )		// action = 0 = Delete
+				levelClone = clone.append( levelStartNode.clone() );
+
+			// The processing of siblings may have already been done by the parent.
+			if ( !startParents[ k ] || levelStartNode.$.parentNode != startParents[ k ].$.parentNode )
+			{
+				currentNode = levelStartNode.getPrevious();
+
+				while( currentNode )
+				{
+					// Stop processing when the current node matches a node in the
+					// startParents tree or if it is the startNode.
+					if ( currentNode.equals( startParents[ k ] ) || currentNode.equals( startNode ) )
+						break;
+
+					// Cache the next sibling.
+					currentSibling = currentNode.getPrevious();
+
+					// If cloning, just clone it.
+					if ( action == 2 )	// 2 = Clone
+						clone.$.insertBefore( currentNode.$.cloneNode( true ), clone.$.firstChild ) ;
+					else
+					{
+						// Both Delete and Extract will remove the node.
+						currentNode.remove();
+
+						// When Extracting, mode the removed node to the docFrag.
+						if ( action == 1 )	// 1 = Extract
+							clone.$.insertBefore( currentNode.$, clone.$.firstChild );
+					}
+
+					currentNode = currentSibling;
+				}
+			}
+
+			if ( clone )
+				clone = levelClone;
+		}
+
+		if ( action == 2 )		// 2 = Clone.
+		{
+			// No changes in the DOM should be done, so fix the split text (if any).
+
+			var startTextNode = range.startContainer;
+			if ( startTextNode.type == CKEDITOR.NODE_TEXT )
+			{
+				startTextNode.$.data += startTextNode.$.nextSibling.data;
+				startTextNode.$.parentNode.removeChild( startTextNode.$.nextSibling );
+			}
+
+			var endTextNode = range.endContainer;
+			if ( endTextNode.type == CKEDITOR.NODE_TEXT && endTextNode.$.nextSibling )
+			{
+				endTextNode.$.data += endTextNode.$.nextSibling.data;
+				endTextNode.$.parentNode.removeChild( endTextNode.$.nextSibling );
+			}
+		}
+		else
+		{
+			// Collapse the range.
+
+			// If a node has been partially selected, collapse the range between
+			// topStart and topEnd. Otherwise, simply collapse it to the start. (W3C specs).
+			if ( topStart && topEnd && ( startNode.$.parentNode != topStart.$.parentNode || endNode.$.parentNode != topEnd.$.parentNode ) )
+			{
+				var endIndex = topEnd.getIndex();
+
+				// If the start node is to be removed, we must correct the
+				// index to reflect the removal.
+				if ( removeStartNode && topEnd.$.parentNode == startNode.$.parentNode )
+					endIndex--;
+
+				range.setStart( topEnd.getParent(), endIndex );
+			}
+
+			// Collapse it to the start.
+			range.collapse( true );
+		}
+
+		// Cleanup any marked node.
+		if( removeStartNode )
+			startNode.remove();
+
+		if( removeEndNode && endNode.$.parentNode )
+			endNode.remove();
+	};
+
+	CKEDITOR.dom.range.prototype =
+	{
+		clone : function()
+		{
+			var clone = new CKEDITOR.dom.range( this.document );
+
+			clone.startContainer = this.startContainer;
+			clone.startOffset = this.startOffset;
+			clone.endContainer = this.endContainer;
+			clone.endOffset = this.endOffset;
+			clone.collapsed = this.collapsed;
+
+			return clone;
+		},
+
+		collapse : function( toStart )
+		{
+			if ( toStart )
+			{
+				this.endContainer	= this.startContainer;
+				this.endOffset		= this.startOffset;
+			}
+			else
+			{
+				this.startContainer	= this.endContainer;
+				this.startOffset	= this.endOffset;
+			}
+
+			this.collapsed = true;
+		},
+
+		// The selection may be lost when cloning (due to the splitText() call).
+		cloneContents : function()
+		{
+			var docFrag = new CKEDITOR.dom.documentFragment( this.document );
+
+			if ( !this.collapsed )
+				execContentsAction( this, 2, docFrag );
+
+			return docFrag;
+		},
+
+		deleteContents : function()
+		{
+			if ( this.collapsed )
+				return;
+
+			execContentsAction( this, 0 );
+		},
+
+		extractContents : function()
+		{
+			var docFrag = new CKEDITOR.dom.documentFragment( this.document );
+
+			if ( !this.collapsed )
+				execContentsAction( this, 1, docFrag );
+
+			return docFrag;
+		},
+
+		// This is an "intrusive" way to create a bookmark. It includes <span> tags
+		// in the range boundaries. The advantage of it is that it is possible to
+		// handle DOM mutations when moving back to the bookmark.
+		// Attention: the inclusion of nodes in the DOM is a design choice and
+		// should not be changed as there are other points in the code that may be
+		// using those nodes to perform operations. See GetBookmarkNode.
+		createBookmark : function()
+		{
+			var startNode, endNode;
+			var clone;
+
+			startNode = this.document.createElement( 'span' );
+			startNode.setAttribute( '_fck_bookmark', 1 );
+			startNode.setStyle( 'display', 'display' );
+
+			// For IE, it must have something inside, otherwise it may be
+			// removed during DOM operations.
+			startNode.setHtml( '&nbsp;' );
+
+			// If collapsed, the endNode will not be created.
+			if ( !this.collapsed )
+			{
+				endNode = startNode.clone();
+
+				clone = this.clone();
+				clone.collapse();
+				clone.insertNode( endNode );
+			}
+
+			clone = this.clone();
+			clone.collapse( true );
+			clone.insertNode( startNode );
+
+			// Update the range position.
+			if ( endNode )
+			{
+				this.setStart( startNode, CKEDITOR.POSITION_AFTER_END );
+				this.setEnd( endNode, CKEDITOR.POSITION_BEFORE_START );
+			}
+			else
+				this.moveToPosition( startNode, CKEDITOR.POSITION_AFTER_END );
+
+			return {
+				startNode : startNode,
+				endNode : endNode
+			};
+		},
+
+		getCommonAncestor : function()
+		{
+			var start = this.startContainer;
+			var end = this.endContainer;
+
+			if ( start.equals( end ) )
+				return start;
+
+			if ( end.type == CKEDITOR.NODE_ELEMENT && end.contains( start ) )
+				return end;
+
+			if ( start.type != CKEDITOR.NODE_ELEMENT )
+				start = start.getParent();
+
+			do
+			{
+				if ( start.contains( end ) )
+					return start;
+			}
+			while( ( start = start.getParent() ) )
+
+			return null;
+		},
+
+		trim : function( ignoreStart, ignoreEnd )
+		{
+			var startContainer = this.startContainer;
+			var startOffset = this.startOffset;
+
+			var endContainer = this.endContainer;
+			var endOffset = this.endOffset;
+
+			if ( !ignoreStart && startContainer.type == CKEDITOR.NODE_TEXT )
+			{
+				// If the offset is zero, we just insert the new node before
+				// the start.
+				if ( !startOffset )
+				{
+					startOffset = startContainer.getIndex();
+					startContainer = startContainer.getParent();
+				}
+				// If the offset is at the end, we'll insert it after the text
+				// node.
+				else if ( startOffset >= startContainer.getLength() )
+				{
+					startOffset = startContainer.getIndex() + 1;
+					startContainer = startContainer.getParent();
+				}
+				// In other case, we split the text node and insert the new
+				// node at the split point.
+				else
+				{
+					var nextText = startContainer.split( startOffset );
+
+					startOffset = startContainer.getIndex() + 1;
+					startContainer = startContainer.getParent();
+
+					// Check if it is necessary to update the end boundary.
+					if ( this.collapsed )
+						this.setEnd( startContainer, startOffset );
+					else if ( this.startContainer.equals( this.endContainer ) )
+						this.setEnd( nextText, this.endOffset - this.startOffset );
+				}
+
+				this.setStart( startContainer, startOffset );
+			}
+
+			if ( !ignoreEnd && !this.collapsed && endContainer.type == CKEDITOR.NODE_TEXT )
+			{
+				// If the offset is zero, we just insert the new node before
+				// the start.
+				if ( !endOffset )
+				{
+					endOffset = endContainer.getIndex();
+					endContainer = endContainer.getParent();
+				}
+				// If the offset is at the end, we'll insert it after the text
+				// node.
+				else if ( endOffset >= endContainer.getLength() )
+				{
+					endOffset = endContainer.getIndex() + 1;
+					endContainer = endContainer.getParent();
+				}
+				// In other case, we split the text node and insert the new
+				// node at the split point.
+				else
+				{
+					endContainer.split( endOffset );
+
+					endOffset = endContainer.getIndex() + 1;
+					endContainer = endContainer.getParent();
+				}
+
+				this.setEnd( endContainer, endOffset );
+			}
+		},
+
+		enlarge : function( unit )
+		{
+			switch ( unit )
+			{
+				case CKEDITOR.ENLARGE_ELEMENT :
+
+					if ( this.collapsed )
+						return;
+
+					// Get the common ancestor.
+					var commonAncestor = this.getCommonAncestor();
+
+					// 1. For each boundary:
+					//		a. Depending on its position, find out the first node to be checked (a sibling) or, if not available, to be enlarge.
+					//		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.
+					// 2.
+
+					var startTop, endTop;
+
+					var enlargeable, sibling, commonReached;
+
+					// Indicates that the node can be added only if whitespace
+					// is available before it.
+					var needsWhiteSpace = false;
+					var isWhiteSpace;
+					var siblingText;
+
+					// Process the start boundary.
+
+					var container = this.startContainer;
+					var offset = this.startOffset;
+
+					if ( container.type == CKEDITOR.NODE_TEXT )
+					{
+						if ( offset )
+						{
+							// Check if there is any non-space text before the
+							// offset. Otherwise, container is null.
+							container = !CKEDITOR.tools.trim( container.substring( 0, offset ) ).length && container;
+
+							// If we found only whitespace in the node, it
+							// means that we'll need more whitespace to be able
+							// to expand. For example, <i> can be expanded in
+							// "A <i> [B]</i>", but not in "A<i> [B]</i>".
+							needsWhiteSpace = !!container;
+						}
+
+						if ( container )
+						{
+							if ( !( sibling = container.getPrevious() ) )
+								enlargeable = container.getParent();
+						}
+					}
+					else
+					{
+						// If we have offset, get the node preceeding it as the
+						// first sibling to be checked.
+						if ( offset )
+							sibling = container.getChild( offset - 1 ) || container.getLast();
+
+						// If there is no sibling, mark the container to be
+						// enlarged.
+						if ( !sibling )
+							enlargeable = container;
+					}
+
+					while ( enlargeable || sibling )
+					{
+						if ( enlargeable && !sibling )
+						{
+							// If we reached the common ancestor, mark the flag
+							// for it.
+							if ( !commonReached && enlargeable.equals( commonAncestor ) )
+								commonReached = true;
+
+							// If we don't need space or this element breaks
+							// the line, then enlarge it.
+							if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
+							{
+								needsWhiteSpace = false;
+
+								// If the common ancestor has been reached,
+								// we'll not enlarge it immediately, but just
+								// mark it to be enlarged later if the end
+								// boundary also enlarges it.
+								if ( commonReached )
+									startTop = enlargeable;
+								else
+									this.setStartBefore( enlargeable );
+							}
+
+							sibling = enlargeable.getPrevious();
+						}
+
+						// Check all sibling nodes preceeding the enlargeable
+						// node. The node wil lbe enlarged only if none of them
+						// blocks it.
+						while ( sibling )
+						{
+							// This flag indicates that this node has
+							// whitespaces at the end.
+							isWhiteSpace = false;
+
+							if ( sibling.type == CKEDITOR.NODE_TEXT )
+							{
+								siblingText = sibling.getText();
+
+								if ( /[^\s]/.test( siblingText ) )
+									sibling = null;
+
+								isWhiteSpace = /\s$/.test( siblingText );
+							}
+							else
+							{
+								// If this is a visible element.
+								if ( sibling.$.offsetWidth > 0 )
+								{
+									// We'll accept it only if we need
+									// whitespace, and this is an inline
+									// element with whitespace only.
+									if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
+									{
+										// It must contains spaces and inline elements only.
+
+										var siblingText = sibling.getText();
+
+										if ( !(/[^\s]/).test( siblingText ) )
+											sibling = null;
+										else
+										{
+											var allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );
+											for ( var i = 0, child ; child = allChildren[ i++ ] ; )
+											{
+												if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
+												{
+													sibling = null;
+													break;
+												}
+											}
+										}
+
+										if ( sibling )
+											isWhiteSpace = !!siblingText.length;
+									}
+									else
+										sibling = null;
+								}
+							}
+
+							// A node with whitespaces has been found.
+							if ( isWhiteSpace )
+							{
+								// Enlarge the last enlargeable node, if we
+								// were waiting for spaces.
+								if ( needsWhiteSpace )
+								{
+									if ( commonReached )
+										startTop = enlargeable;
+									else
+										this.setStartBefore( enlargeable );
+								}
+								else
+									needsWhiteSpace = true;
+							}
+
+							if ( sibling )
+							{
+								var next = sibling.getPrevious();
+
+								if ( !enlargeable && !next )
+								{
+									// Set the sibling as enlargeable, so it's
+									// parent will be get later outside this while.
+									enlargeable = sibling;
+									sibling = null;
+									break;
+								}
+
+								sibling = next;
+							}
+							else
+							{
+								// If sibling has been set to null, then we
+								// need to stop enlarging.
+								enlargeable = null;
+							}
+						}
+
+						if ( enlargeable )
+							enlargeable = enlargeable.getParent();
+					}
+
+					// Process the end boundary. This is basically the same
+					// code used for the start boundary, with small changes to
+					// make it work in the oposite side (to the right). This
+					// makes it difficult to reuse the code here. So, fixes to
+					// the above code are likely to be replicated here.
+
+					container = this.endContainer;
+					offset = this.endOffset;
+
+					// Reset the common variables.
+					enlargeable = sibling = null;
+					commonReached = needsWhiteSpace = false;
+
+					if ( container.type == CKEDITOR.NODE_TEXT )
+					{
+						// Check if there is any non-space text after the
+						// offset. Otherwise, container is null.
+						container = !CKEDITOR.tools.trim( container.substring( offset ) ).length && container;
+
+						// If we found only whitespace in the node, it
+						// means that we'll need more whitespace to be able
+						// to expand. For example, <i> can be expanded in
+						// "A <i> [B]</i>", but not in "A<i> [B]</i>".
+						needsWhiteSpace = !( container && container.getLength() );
+
+						if ( container )
+						{
+							if ( !( sibling = container.getNext() ) )
+								enlargeable = container.getParent();
+						}
+					}
+					else
+					{
+						if ( offset )
+						sibling = container.getChild( offset );
+
+						if ( !sibling )
+							enlargeable = container;
+					}
+
+					while ( enlargeable || sibling )
+					{
+						if ( enlargeable && !sibling )
+						{
+							if ( !commonReached && enlargeable.equals( commonAncestor ) )
+								commonReached = true;
+
+							if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
+							{
+								needsWhiteSpace = false;
+
+								if ( commonReached )
+									endTop = enlargeable;
+								else
+									this.setEndAfter( enlargeable );
+							}
+
+							sibling = enlargeable.getNext();
+						}
+
+						while ( sibling )
+						{
+							isWhiteSpace = false;
+
+							if ( sibling.type == CKEDITOR.NODE_TEXT )
+							{
+								siblingText = sibling.getText();
+
+								if ( /[^\s]/.test( siblingText ) )
+									sibling = null;
+
+								isWhiteSpace = /^\s/.test( siblingText );
+							}
+							else
+							{
+								// If this is a visible element.
+								if ( sibling.$.offsetWidth > 0 )
+								{
+									// We'll accept it only if we need
+									// whitespace, and this is an inline
+									// element with whitespace only.
+									if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
+									{
+										// It must contains spaces and inline elements only.
+
+										var siblingText = sibling.getText();
+
+										if ( !(/[^\s]/).test( siblingText ) )
+											sibling = null;
+										else
+										{
+											var allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );
+											for ( var i = 0, child ; child = allChildren[ i++ ] ; )
+											{
+												if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
+												{
+													sibling = null;
+													break;
+												}
+											}
+										}
+
+										if ( sibling )
+											isWhiteSpace = !!siblingText.length;
+									}
+									else
+										sibling = null;
+								}
+							}
+
+							if ( isWhiteSpace )
+							{
+								if ( needsWhiteSpace )
+								{
+									if ( commonReached )
+										endTop = enlargeable;
+									else
+										this.setEndAfter( enlargeable );
+								}
+							}
+
+							if ( sibling )
+							{
+								var next = sibling.getNext();
+
+								if ( !enlargeable && !next )
+								{
+									enlargeable = sibling;
+									sibling = null;
+									break;
+								}
+
+								sibling = next;
+							}
+							else
+							{
+								// If sibling has been set to null, then we
+								// need to stop enlarging.
+								enlargeable = null;
+							}
+						}
+
+						if ( enlargeable )
+							enlargeable = enlargeable.getParent();
+					}
+
+					// If the common ancestor can be enlarged by both boundaries, then include it also.
+					if ( startTop && endTop )
+					{
+						commonAncestor = startTop.contains( endTop ) ? endTop : startTop;
+
+						this.setStartBefore( commonAncestor );
+						this.setEndAfter( commonAncestor );
+					}
+			}
+		},
+
+		/**
+		 * Inserts a node at the start of the range. The range will be expanded
+		 * the contain the node.
+		 */
+		insertNode : function( node )
+		{
+			this.trim( false, true );
+
+			var startContainer = this.startContainer;
+			var startOffset = this.startOffset;
+
+			var nextNode = startContainer.getChild( startOffset );
+
+			if ( nextNode )
+				node.insertBefore( nextNode );
+			else
+				startContainer.append( node );
+
+			// Check if we need to update the end boundary.
+			if ( node.getParent().equals( this.endContainer ) )
+				this.endOffset++;
+
+			// Expand the range to embrace the new node.
+			this.setStartBefore( node );
+		},
+
+		moveToPosition : function( node, position )
+		{
+			this.setStartAt( node, position );
+			this.collapse( true );
+		},
+
+		selectNodeContents : function( node )
+		{
+			this.setStart( node, 0 );
+			this.setEnd( node, node.type == CKEDITOR.NODE_TEXT ? node.getLength() : node.getChildCount() );
+		},
+
+		/**
+		 * Sets the start position of a Range.
+		 * @param {CKEDITOR.dom.node} startNode The node to start the range.
+		 * @param {Number} startOffset An integer greater than or equal to zero
+		 *		representing the offset for the start of the range from the start
+		 *		of startNode.
+		 */
+		setStart : function( startNode, startOffset )
+		{
+			// W3C requires a check for the new position. If it is after the end
+			// boundary, the range should be collapsed to the new start. It seams
+			// we will not need this check for our use of this class so we can
+			// ignore it for now.
+
+			this.startContainer	= startNode;
+			this.startOffset	= startOffset;
+
+			if ( !this.endContainer )
+			{
+				this.endContainer	= startNode;
+				this.endOffset		= startOffset;
+			}
+
+			updateCollapsed( this );
+		},
+
+		/**
+		 * Sets the end position of a Range.
+		 * @param {CKEDITOR.dom.node} endNode The node to end the range.
+		 * @param {Number} endOffset An integer greater than or equal to zero
+		 *		representing the offset for the end of the range from the start
+		 *		of endNode.
+		 */
+		setEnd : function( endNode, endOffset )
+		{
+			// W3C requires a check for the new position. If it is before the start
+			// boundary, the range should be collapsed to the new end. It seams we
+			// will not need this check for our use of this class so we can ignore
+			// it for now.
+
+			this.endContainer	= endNode;
+			this.endOffset		= endOffset;
+
+			if ( !this.startContainer )
+			{
+				this.startContainer	= endNode;
+				this.startOffset	= endOffset;
+			}
+
+			updateCollapsed( this );
+		},
+
+		setStartAfter : function( node )
+		{
+			this.setStart( node.getParent(), node.getIndex() + 1 );
+		},
+
+		setStartBefore : function( node )
+		{
+			this.setStart( node.getParent(), node.getIndex() );
+		},
+
+		setEndAfter : function( node )
+		{
+			this.setEnd( node.getParent(), node.getIndex() + 1 );
+		},
+
+		setEndBefore : function( node )
+		{
+			this.setEnd( node.getParent(), node.getIndex() );
+		},
+
+		setStartAt : function( node, position )
+		{
+			switch( position )
+			{
+				case CKEDITOR.POSITION_AFTER_START :
+					this.setStart( node, 0 );
+					break;
+
+				case CKEDITOR.POSITION_BEFORE_END :
+					if ( node.type == CKEDITOR.NODE_TEXT )
+						this.setStart( node, node.getLength() );
+					else
+						this.setStart( node, node.getChildCount() );
+					break;
+
+				case CKEDITOR.POSITION_BEFORE_START :
+					this.setStartBefore( node );
+					break;
+
+				case CKEDITOR.POSITION_AFTER_END :
+					this.setStartAfter( node );
+			}
+
+			updateCollapsed( this );
+		},
+
+		setEndAt : function( node, position )
+		{
+			switch( position )
+			{
+				case CKEDITOR.POSITION_AFTER_START :
+					this.setEnd( node, 0 );
+					break;
+
+				case CKEDITOR.POSITION_BEFORE_END :
+					if ( node.type == CKEDITOR.NODE_TEXT )
+						this.setEnd( node, node.getLength() );
+					else
+						this.setEnd( node, node.getChildCount() );
+					break;
+
+				case CKEDITOR.POSITION_BEFORE_START :
+					this.setEndBefore( node );
+					break;
+
+				case CKEDITOR.POSITION_AFTER_END :
+					this.setEndAfter( node );
+			}
+
+			updateCollapsed( this );
+		}
+	};
+})();
+
+CKEDITOR.POSITION_AFTER_START	= 1;	// <element>^contents</element>		"^text"
+CKEDITOR.POSITION_BEFORE_END	= 2;	// <element>contents^</element>		"text^"
+CKEDITOR.POSITION_BEFORE_START	= 3;	// ^<element>contents</element>		^"text"
+CKEDITOR.POSITION_AFTER_END		= 4;	// <element>contents</element>^		"text"
+
+CKEDITOR.ENLARGE_ELEMENT = 1;
Index: /CKEditor/branches/prototype/_source/core/dom/text.js
===================================================================
--- /CKEditor/branches/prototype/_source/core/dom/text.js	(revision 2572)
+++ /CKEditor/branches/prototype/_source/core/dom/text.js	(revision 2573)
@@ -62,2 +62,56 @@
 
 CKEDITOR.dom.text.prototype = new CKEDITOR.dom.node();
+
+CKEDITOR.tools.extend( CKEDITOR.dom.text.prototype,
+	/** @lends CKEDITOR.dom.text.prototype */
+	{
+		/**
+		 * The node type. This is a constant value set to
+		 * {@link CKEDITOR.NODE_TEXT}.
+		 * @type Number
+		 * @example
+		 */
+		type : CKEDITOR.NODE_TEXT,
+		
+		getLength : function()
+		{
+			return this.$.nodeValue.length;
+		},
+
+		getText : function()
+		{
+			return this.$.nodeValue;
+		},
+		
+		/**
+		 * Breaks this text node into two nodes at the specified offset,
+		 * keeping both in the tree as siblings. This node then only contains
+		 * all the content up to the offset point. A new text node, which is
+		 * inserted as the next sibling of this node, contains all the content
+		 * at and after the offset point. When the offset is equal to the
+		 * length of this node, the new node has no data.
+		 * @param {Number} The position at which to split, starting from zero.
+		 * @returns {CKEDITOR.dom.text} The new text node.
+		 */
+		split : function( offset )
+		{
+			return new CKEDITOR.dom.text( this.$.splitText( offset ) );
+		},
+		
+		/**
+		 * Extracts characters from indexA up to but not including indexB.
+		 * @param {Number} indexA An integer between 0 and one less than the
+		 *		length of the text. 
+		 * @param {Number} [indexB] An integer between 0 and the length of the
+		 *		string. If omitted, extracts characters to the end of the text.
+		 */
+		substring : function( indexA, indexB )
+		{
+			// We need the following check due to a Firefox bug
+			// https://bugzilla.mozilla.org/show_bug.cgi?id=458886
+			if ( typeof indexB != 'number' )
+				return this.$.nodeValue.substr( indexA );
+			else
+				return this.$.nodeValue.substring( indexA, indexB );
+		}
+	});
Index: /CKEditor/branches/prototype/_source/core/loader.js
===================================================================
--- /CKEditor/branches/prototype/_source/core/loader.js	(revision 2572)
+++ /CKEditor/branches/prototype/_source/core/loader.js	(revision 2573)
@@ -40,5 +40,5 @@
 		var scripts =
 		{
-			'core/_bootstrap'		: [ 'core/config', 'core/ckeditor', 'core/plugins', 'core/scriptloader', 'core/tools', /* The following are entries that we wnat to force loading to at the end to avoid dependence recursion */ 'core/dom/text' ],
+			'core/_bootstrap'		: [ 'core/config', 'core/ckeditor', 'core/plugins', 'core/scriptloader', 'core/tools', /* The following are entries that we wnat to force loading to at the end to avoid dependence recursion */ 'core/dom/text', 'core/dom/range' ],
 			'core/ajax'				: [ 'core/xml' ],
 			'core/ckeditor'			: [ 'core/ajax', 'core/ckeditor_basic', 'core/dom', 'core/dtd', 'core/dom/document', 'core/dom/element', 'core/editor', 'core/event', 'core/htmlparser', 'core/htmlparser/element', 'core/htmlparser/fragment', 'core/tools' ],
@@ -48,8 +48,10 @@
 			'core/dom'				: [],
 			'core/dom/document'		: [ 'core/dom','core/dom/element', 'core/dom/domobject', 'core/dom/window' ],
+			'core/dom/documentfragment'	: [ 'core/dom/element' ],
 			'core/dom/element'		: [ 'core/dom', 'core/dom/document', 'core/dom/domobject', 'core/dom/node', 'core/tools' ],
 			'core/dom/event'		: [],
 			'core/dom/node'			: [ 'core/dom/domobject', 'core/tools' ],
 			'core/dom/domobject'	: [ 'core/dom/event' ],
+			'core/dom/range'		: [ 'core/dom/document', 'core/dom/documentfragment', 'core/dom/element' ],
 			'core/dom/text'			: [ 'core/dom/node', 'core/dom/domobject' ],
 			'core/dom/window'		: [ 'core/dom/domobject' ],
Index: /CKEditor/branches/prototype/_source/core/test.js
===================================================================
--- /CKEditor/branches/prototype/_source/core/test.js	(revision 2572)
+++ /CKEditor/branches/prototype/_source/core/test.js	(revision 2573)
@@ -73,10 +73,10 @@
 	 * Gets the inner HTML of an element, for testing purposes.
 	 */
-	getInnerHtml : function( elementId )
+	getInnerHtml : function( elementOrId )
 	{
-		var html = document.getElementById( elementId ).innerHTML;
+		var html = ( elementOrId.nodeType ? elementOrId : document.getElementById( elementOrId ) ).innerHTML;
 		html = html.toLowerCase();
-		html = html.replace( /"/g, '' );
-		html = html.replace( /\s+_cke_expando=\d+/, '' );
+		html = html.replace( /["\n\r]/g, '' );
+		html = html.replace( /\s+_cke_expando=\d+/g, '' );
 		return html;
 	}
Index: /CKEditor/branches/prototype/_source/tests/core/dom/element.html
===================================================================
--- /CKEditor/branches/prototype/_source/tests/core/dom/element.html	(revision 2572)
+++ /CKEditor/branches/prototype/_source/tests/core/dom/element.html	(revision 2573)
@@ -444,4 +444,32 @@
 			
 			assert.areNotSame( a._, b._ );
+		},
+		
+		test_getText1 : function()
+		{
+			var element = new CKEDITOR.dom.element( document.getElementById( 'getText' ) );
+			
+			// IE gives us a different result, which is ok for us (see code comments).
+			if ( CKEDITOR.env.ie )
+				assert.areSame( 'Some \nsample text for testing', element.getText().replace( /\r\n|\r/g, '\n' ) );
+			else
+				assert.areSame( 'Some  sample text    for    testing', element.getText() );
+		},
+
+		test_getText2 : function()
+		{
+			var element = new CKEDITOR.dom.element( document.getElementById( 'getText2' ).getElementsByTagName( 'b' )[0] );
+			assert.areSame( ' ', element.getText() );
+		},
+
+		test_getText3 : function()
+		{
+			var element = new CKEDITOR.dom.element( document.getElementById( 'getText3' ) );
+			
+			// IE gives us a different result, which is ok for us (see code comments).
+			if ( CKEDITOR.env.ie )
+				assert.areSame( 'A B', element.getText() );
+			else
+				assert.areSame( 'A\nB', element.getText().replace( /\r\n|\r/g, '\n' ) );
 		},
 
@@ -468,4 +496,8 @@
 	<input id="tabIndexInput20" tabindex="20" />
 	<script id="tabIndexScriptDef" type="text/javascript"></script>
+	<div id="getText">Some <br /><b> sample text</b>    for <i>   testing</i></div>
+	<div id="getText2">A<b> </b>B</div>
+	<div id="getText3">A
+B</div>
 </body>
 </html>
Index: /CKEditor/branches/prototype/_source/tests/core/dom/range.html
===================================================================
--- /CKEditor/branches/prototype/_source/tests/core/dom/range.html	(revision 2573)
+++ /CKEditor/branches/prototype/_source/tests/core/dom/range.html	(revision 2573)
@@ -0,0 +1,1672 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>CKEDITOR.dom.range</title>
+	<link rel="stylesheet" type="text/css" href="../../test.css" />
+	<script type="text/javascript" src="../../../../ckeditor_source.js"></script>
+	<script type="text/javascript" src="../../test.js"></script>
+	<script type="text/javascript">
+	//<![CDATA[
+
+var html1, html2;
+
+CKEDITOR.test.addTestCase( (function()
+{
+	// Local references.
+	var assert			= CKEDITOR.test.assert;
+	var getInnerHtml	= CKEDITOR.test.getInnerHtml;
+
+	var doc = new CKEDITOR.dom.document( document );
+
+	return {
+		test__constructor : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+
+			assert.isNotNull( range, 'range must not be null' );
+
+			assert.isNull( range.startContainer, 'startContainer must be null' );
+			assert.isNull( range.startOffset, 'startOffset must be null' );
+			assert.isNull( range.endContainer, 'endContainer must be null' );
+			assert.isNull( range.endOffset, 'endOffset must be null' );
+
+			assert.isTrue( range.collapsed, 'range must be collapsed' );
+		},
+
+		test_setStart : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( 'playground' ), 1 );
+
+			assert.areSame( document.getElementById( 'playground' ), range.startContainer.$ );
+			assert.areSame( 1, range.startOffset );
+			assert.areSame( document.getElementById( 'playground' ), range.endContainer.$ );
+			assert.areSame( 1, range.endOffset, 'range.endOffset' );
+			assert.isTrue( range.collapsed );
+		},
+
+		test_setEnd : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+			range.setEnd( doc.getById( 'playground' ), 1 );
+
+			assert.areSame( document.getElementById( 'playground' ), range.startContainer.$ );
+			assert.areSame( 1, range.startOffset );
+			assert.areSame( document.getElementById( 'playground' ), range.endContainer.$ );
+			assert.areSame( 1, range.endOffset, 'range.endOffset' );
+			assert.isTrue( range.collapsed );
+		},
+
+		test_setStartAfter : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+			range.setStartAfter( doc.getById( '_B' ) );
+			range.setStartAfter( doc.getById( '_H1' ).getFirst() );
+
+			assert.areSame( document.getElementById( '_H1' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_Para' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 2, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_setStartBefore : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+			range.setStartBefore( doc.getById( '_B' ) );
+			range.setStartBefore( doc.getById( '_H1' ).getFirst() );
+
+			assert.areSame( document.getElementById( '_H1' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_Para' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 1, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_setEndAfter : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+			range.setEndAfter( doc.getById( '_H1' ).getFirst() );
+			range.setEndAfter( doc.getById( '_B' ) );
+
+			assert.areSame( document.getElementById( '_H1' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_Para' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 2, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_setEndBefore : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+			range.setEndBefore( doc.getById( '_H1' ).getFirst() );
+			range.setEndBefore( doc.getById( '_B' ) );
+
+			assert.areSame( document.getElementById( '_H1' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_Para' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 1, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_setStartAt_1 : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+			range.setStartAt( doc.getById('_Span'), CKEDITOR.POSITION_AFTER_START );
+
+			assert.areSame( document.getElementById('_Span'), range.startContainer.$, 'startContainer' );
+			assert.areSame( document.getElementById('_Span'), range.endContainer.$, 'endContainer' );
+			assert.isTrue( range.collapsed, 'collapsed' );
+		},
+
+		test_setStartAt_2 : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+			range.setStartAt( doc.getById('_Span'), CKEDITOR.POSITION_BEFORE_END );
+
+			assert.areSame( document.getElementById('_Span'), range.startContainer.$, 'startContainer' );
+			assert.areSame( document.getElementById('_Span'), range.endContainer.$, 'endContainer' );
+			assert.isTrue( range.collapsed, 'collapsed' );
+		},
+
+		test_setStartAt_3 : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+			range.setStartAt( doc.getById('_Span'), CKEDITOR.POSITION_BEFORE_START );
+
+			assert.areSame( document.getElementById('_P'), range.startContainer.$, 'startContainer' );
+			assert.areSame( document.getElementById('_P'), range.endContainer.$, 'endContainer' );
+			assert.isTrue( range.collapsed, 'collapsed' );
+		},
+
+		test_setStartAt_4 : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+			range.setStartAt( doc.getById('_Span'), CKEDITOR.POSITION_AFTER_END );
+
+			assert.areSame( document.getElementById('_P'), range.startContainer.$, 'startContainer' );
+			assert.areSame( document.getElementById('_P'), range.endContainer.$, 'endContainer' );
+			assert.isTrue( range.collapsed, 'collapsed' );
+		},
+
+		test_setEndAt_1 : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+			range.setEndAt( doc.getById('_Span'), CKEDITOR.POSITION_AFTER_START );
+
+			assert.areSame( document.getElementById('_Span'), range.startContainer.$, 'startContainer' );
+			assert.areSame( document.getElementById('_Span'), range.endContainer.$, 'endContainer' );
+			assert.isTrue( range.collapsed, 'collapsed' );
+		},
+
+		test_setEndAt_2 : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+			range.setEndAt( doc.getById('_Span'), CKEDITOR.POSITION_BEFORE_END );
+
+			assert.areSame( document.getElementById('_Span'), range.startContainer.$, 'startContainer' );
+			assert.areSame( document.getElementById('_Span'), range.endContainer.$, 'endContainer' );
+			assert.isTrue( range.collapsed, 'collapsed' );
+		},
+
+		test_setEndAt_3 : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+			range.setEndAt( doc.getById('_Span'), CKEDITOR.POSITION_BEFORE_START );
+
+			assert.areSame( document.getElementById('_P'), range.startContainer.$, 'startContainer' );
+			assert.areSame( document.getElementById('_P'), range.endContainer.$, 'endContainer' );
+			assert.isTrue( range.collapsed, 'collapsed' );
+		},
+
+		test_setEndAt_4 : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+			range.setEndAt( doc.getById('_Span'), CKEDITOR.POSITION_AFTER_END );
+
+			assert.areSame( document.getElementById('_P'), range.startContainer.$, 'startContainer' );
+			assert.areSame( document.getElementById('_P'), range.endContainer.$, 'endContainer' );
+			assert.isTrue( range.collapsed, 'collapsed' );
+		},
+
+		test_collapsed : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+			range.setStartAt( doc.getById( '_P' ), CKEDITOR.POSITION_AFTER_START );
+
+			assert.isTrue( range.collapsed );
+		},
+
+		test_collapse : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+			range.setStartAt( doc.getById( '_Para' ), CKEDITOR.POSITION_AFTER_START );
+			range.setEndAt( doc.getById( '_Span' ), CKEDITOR.POSITION_BEFORE_END );
+
+			assert.areSame( document.getElementById('_Para'), range.startContainer.$, 'startContainer' );
+			assert.areSame( document.getElementById('_Span'), range.endContainer.$, 'endContainer' );
+			assert.isFalse( range.collapsed, 'collapsed' );
+
+			range.collapse( true );
+
+			assert.areSame( document.getElementById('_Para'), range.startContainer.$, 'startContainer' );
+			assert.areSame( document.getElementById('_Para'), range.endContainer.$, 'endContainer' );
+			assert.isTrue( range.collapsed, 'collapsed' );
+		},
+
+		test_selectNodeContents_Element : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+
+			// Test with an Element node.
+			range.selectNodeContents( doc.getById( '_Para' ) );
+
+			assert.areSame( document.getElementById( '_Para' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_Para' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 3, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_selectNodeContents_Text : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+
+			// Test with a Text node.
+			range.selectNodeContents( doc.getById( '_Para' ).getFirst() );
+
+			assert.areSame( document.getElementById( '_Para' ).firstChild, range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_Para' ).firstChild, range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 8, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_collapse_ToStart : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+
+			range.selectNodeContents( doc.getById( '_P' ) );
+			range.collapse( true );
+
+			assert.areSame( document.getElementById( '_P' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_P' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 0, range.endOffset, 'range.endOffset' );
+			assert.isTrue( range.collapsed, 'range.collapsed' );
+		},
+
+		test_collapse_ToEnd : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+
+			range.selectNodeContents( doc.getById( '_Para' ) );
+			range.collapse( false );
+
+			assert.areSame( document.getElementById( '_Para' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 3, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_Para' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 3, range.endOffset, 'range.endOffset' );
+			assert.isTrue( range.collapsed, 'range.collapsed' );
+		},
+
+		test_insertNode_ElementContents : function()
+		{
+			var newNode = new CKEDITOR.dom.element( 'span' );
+			newNode.setHtml( 'test_' );
+
+			var range = new CKEDITOR.dom.range( document );
+
+			range.selectNodeContents( doc.getById( '_B' ) );
+			range.insertNode( newNode );
+
+			assert.areSame( document.getElementById( '_B' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_B' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 2, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_insertNode_ElementCollapsed : function()
+		{
+			var newNode = new CKEDITOR.dom.element( 'span' );
+			newNode.setHtml( 'test_' );
+
+			var range = new CKEDITOR.dom.range( document );
+
+			range.setStartBefore( doc.getById( '_Para' ) );
+			range.insertNode( newNode );
+
+			assert.areSame( document.getElementById( 'playground' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( 'playground' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 2, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_insertNode_ElementNotCollapsed : function()
+		{
+			var newNode = new CKEDITOR.dom.element( 'span' );
+			newNode.setHtml( 'test_' );
+
+			var range = new CKEDITOR.dom.range( document );
+
+			range.setStartBefore( doc.getById( '_Para' ) );
+			range.setStartBefore( doc.getById( '_H1' ) );
+			range.insertNode( newNode );
+
+			assert.areSame( document.getElementById( 'playground' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( 'playground' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 2, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_insertNode_DiffElements : function()
+		{
+			var newNode = new CKEDITOR.dom.element( 'span' );
+			newNode.setHtml( 'test_' );
+
+			var range = new CKEDITOR.dom.range( document );
+
+			range.selectNodeContents( doc.getById( '_Para' ) );
+
+			range.setStart( doc.getById( '_H1' ), 0 );
+			range.insertNode( newNode );
+
+			assert.areSame( document.getElementById( '_H1' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_Para' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 3, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+
+			assert.isTrue( range.startContainer.getChild( range.startOffset ).equals( newNode ), 'Start must be on new node' );
+		},
+
+		test_insertNode_TextCollapsed : function()
+		{
+			var newNode = new CKEDITOR.dom.element( 'span' );
+			newNode.setHtml( 'test_' );
+
+			var range = new CKEDITOR.dom.range( document );
+
+			range.setStart( doc.getById( '_H1' ).getFirst(), 3 );
+			range.insertNode( newNode );
+
+			assert.areSame( document.getElementById( '_H1' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_H1' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 2, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_insertNode_TextNotCollapsed : function()
+		{
+			var newNode = new CKEDITOR.dom.element( 'span' );
+			newNode.setHtml( 'test_' );
+
+			var range = new CKEDITOR.dom.range( document );
+
+			range.setStart( doc.getById( '_H1' ).getFirst(), 3 );
+			range.setEnd( doc.getById( '_H1' ).getFirst(), 5 );
+			range.insertNode( newNode );
+
+			assert.areSame( document.getElementById( '_H1' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_H1' ).childNodes[2], range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 2, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_insertNode_Mixed : function()
+		{
+			var newNode = new CKEDITOR.dom.element( 'span' );
+			newNode.setHtml( 'test_' );
+
+			var range = new CKEDITOR.dom.range( document );
+
+			range.setStart( doc.getById( '_H1' ).getFirst(), 0 );
+			range.setEnd( doc.getById( '_P' ), 1 );
+			range.insertNode( newNode );
+
+			assert.areSame( document.getElementById( '_H1' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_P' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 1, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_getCommonAncestor1 : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_H1' ).getFirst(), 3 );
+
+			assert.areSame( document.getElementById( '_H1' ).firstChild, range.getCommonAncestor().$ );
+		},
+
+		test_getCommonAncestor2 : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_H1' ), 0 );
+
+			assert.areSame( document.getElementById( '_H1' ), range.getCommonAncestor().$ );
+		},
+
+		test_getCommonAncestor3 : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_H1' ), 0 );
+			range.setEnd( doc.getById( '_Para' ), 0 );
+
+			assert.areSame( document.getElementById( 'playground' ), range.getCommonAncestor().$ );
+		},
+
+		test_getCommonAncestor4 : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_Para' ).getFirst(), 1 );
+			range.setEnd( doc.getById( '_B' ), 0 );
+
+			assert.areSame( document.getElementById( '_Para' ), range.getCommonAncestor().$ );
+		},
+
+		test_getCommonAncestor5 : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getBody(), 0 );
+			range.setEnd( doc.getById( '_B' ).getFirst(), 1 );
+
+			assert.areSame( document.body, range.getCommonAncestor().$ );
+		},
+
+		test_getCommonAncestor6 : function()
+		{
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_EnlargeI' ).getFirst(), 2 );
+			range.setEnd( doc.getById( '_EnlargeB' ), 3 );
+
+			assert.areSame( document.getElementById( '_EnlargeB' ), range.getCommonAncestor().$ );
+		},
+
+		test_enlarge_element1 : function()
+		{
+			// For IE, see the next test.
+			if ( CKEDITOR.env.ie )
+				return;
+
+			// <p> Test <b> <i>  [Enlarge]</i> this</b>   </p>
+			// <p> Test <b> [<i>  Enlarge</i>] this</b>   </p>
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_EnlargeI' ).getFirst(), 2 );
+			range.setEnd( doc.getById( '_EnlargeI' ), 1 );
+
+			range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
+
+			assert.areSame( document.getElementById( '_EnlargeB' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_EnlargeB' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 2, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_enlarge_element1_ie : function()
+		{
+			// IE normalizes whitespaces when parsing the HTML, so we need a
+			// specific test for it.
+			if ( !CKEDITOR.env.ie )
+				return;
+
+			// <p>Test <b><i>[Enlarge]</i> this</b></p>
+			// <p>Test <b>[<i>Enlarge</i>] this</b></p>
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_EnlargeI' ).getFirst(), 0 );
+			range.setEnd( doc.getById( '_EnlargeI' ), 1 );
+
+			range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
+
+			assert.areSame( document.getElementById( '_EnlargeB' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_EnlargeB' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 1, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_enlarge_element2 : function()
+		{
+			// For IE, see the next test.
+			if ( CKEDITOR.env.ie )
+				return;
+
+			// <p> Test <b> <i>  [Enlarge</i> this]</b>   </p>
+			// <p> Test [<b> <i>  Enlarge</i> this</b>]   </p>
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_EnlargeI' ).getFirst(), 2 );
+			range.setEnd( doc.getById( '_EnlargeB' ), 3 );
+
+			range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
+
+			assert.areSame( document.getElementById( '_EnlargeP' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_EnlargeP' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 2, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_enlarge_element2_ie : function()
+		{
+			// IE normalizes whitespaces when parsing the HTML, so we need a
+			// specific test for it.
+			if ( !CKEDITOR.env.ie )
+				return;
+
+			// <p>Test <b><i>[Enlarge</i> this]</b></p>
+			// <p>Test [<b><i>Enlarge</i> this</b>]</p>
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_EnlargeI' ).getFirst(), 0 );
+			range.setEnd( doc.getById( '_EnlargeB' ), 2 );
+
+			range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
+
+			assert.areSame( document.getElementById( '_EnlargeP' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_EnlargeP' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 2, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_enlarge_element3 : function()
+		{
+			// For IE, see the next test.
+			if ( CKEDITOR.env.ie )
+				return;
+
+			// <p> [Test <b> <i>  Enlarge]</i> this</b>   </p>
+			// <p> [Test <b> <i>  Enlarge</i>] this</b>   </p>
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_EnlargeP' ).getFirst(), 1 );
+			range.setEnd( doc.getById( '_EnlargeI' ), 1 );
+
+			range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
+
+			assert.areSame( document.getElementById( '_EnlargeP' ).firstChild, range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_EnlargeB' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 2, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_enlarge_element3_ie : function()
+		{
+			// IE normalizes whitespaces when parsing the HTML, so we need a
+			// specific test for it.
+			if ( !CKEDITOR.env.ie )
+				return;
+
+			// <p>[Test <b><i>Enlarge]</i> this</b></p>
+			// <p>[Test <b><i>Enlarge</i>] this</b></p>
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_EnlargeP' ).getFirst(), 0 );
+			range.setEnd( doc.getById( '_EnlargeI' ), 1 );
+
+			range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
+
+			assert.areSame( document.getElementById( '_EnlargeP' ).firstChild, range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_EnlargeB' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 1, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_enlarge_element4 : function()
+		{
+			// For IE, see the next test.
+			if ( CKEDITOR.env.ie )
+				return;
+
+			// <p> [Test <b> <i>  Enlarge</i> this]</b>   </p>
+			// [<p> Test <b> <i>  Enlarge</i> this</b>   </p>]
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_EnlargeP' ).getFirst(), 1 );
+			range.setEnd( doc.getById( '_EnlargeB' ).getChild( 2 ), 5 );
+
+			range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
+
+			assert.areSame( document.getElementById( '_EnlargeP' ).parentNode, range.startContainer.$, 'range.startContainer' );
+			assert.areSame( doc.getById( '_EnlargeP' ).getIndex(), range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_EnlargeP' ).parentNode, range.endContainer.$, 'range.endContainer' );
+			assert.areSame( doc.getById( '_EnlargeP' ).getIndex() + 1, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_enlarge_element4_ie : function()
+		{
+			// IE normalizes whitespaces when parsing the HTML, so we need a
+			// specific test for it.
+			if ( !CKEDITOR.env.ie )
+				return;
+
+			// <p>[Test <b><i>Enlarge</i> this]</b></p>
+			// [<p>Test <b><i>Enlarge</i> this</b></p>]
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_EnlargeP' ).getFirst(), 0 );
+			range.setEnd( doc.getById( '_EnlargeB' ).getChild( 1 ), 5 );
+
+			range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
+
+			assert.areSame( document.getElementById( '_EnlargeP' ).parentNode, range.startContainer.$, 'range.startContainer' );
+			assert.areSame( doc.getById( '_EnlargeP' ).getIndex(), range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_EnlargeP' ).parentNode, range.endContainer.$, 'range.endContainer' );
+			assert.areSame( doc.getById( '_EnlargeP' ).getIndex() + 1, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_enlarge_element5 : function()
+		{
+			// For IE, see the next test.
+			if ( CKEDITOR.env.ie )
+				return;
+
+			// <p> Test<b> <i>  [Enlarge</i> this]</b>   </p>
+			// <p> Test<b> [<i>  Enlarge</i> this]</b>   </p>
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_EnlargeI1' ).getFirst(), 2 );
+			range.setEnd( doc.getById( '_EnlargeB1' ).getChild( 2 ), 5 );
+
+			range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
+
+			assert.areSame( document.getElementById( '_EnlargeB1' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_EnlargeB1' ).childNodes[ 2 ], range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 5, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_enlarge_element5_ie : function()
+		{
+			// IE normalizes whitespaces when parsing the HTML, so we need a
+			// specific test for it.
+			if ( !CKEDITOR.env.ie )
+				return;
+
+			// <p>Test<b> <i>[Enlarge</i> this]</b></p>
+			// <p>Test<b> [<i>Enlarge</i> this]</b></p>
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_EnlargeI1' ).getFirst(), 0 );
+			range.setEnd( doc.getById( '_EnlargeB1' ).getChild( 2 ), 5 );
+
+			range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
+
+			assert.areSame( document.getElementById( '_EnlargeB1' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_EnlargeB1' ).childNodes[ 2 ], range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 5, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_enlarge_element6 : function()
+		{
+			// For IE, see the next test.
+			if ( CKEDITOR.env.ie )
+				return;
+
+			// <p> <b> <i>  [Enlarge</i>] this</b>   </p>
+			// <p> <b> [<i>  Enlarge</i>] this</b>   </p>
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_EnlargeI2' ).getFirst(), 2 );
+			range.setEnd( doc.getById( '_EnlargeB2' ), 2 );
+
+			range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
+
+			assert.areSame( document.getElementById( '_EnlargeB2' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_EnlargeB2' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 2, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_enlarge_element6_ie : function()
+		{
+			// IE normalizes whitespaces when parsing the HTML, so we need a
+			// specific test for it.
+			if ( !CKEDITOR.env.ie )
+				return;
+
+			// <p><b><i>[Enlarge</i>] this</b></p>
+			// <p><b>[<i>Enlarge</i>] this</b></p>
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_EnlargeI2' ).getFirst(), 0 );
+			range.setEnd( doc.getById( '_EnlargeB2' ), 1 );
+
+			range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
+
+			assert.areSame( document.getElementById( '_EnlargeB2' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_EnlargeB2' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 1, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_enlarge_element7 : function()
+		{
+			// For IE, see the next test.
+			if ( CKEDITOR.env.ie )
+				return;
+
+			// <p> <b> <i>  [Enlarge</i> this]</b>   </p>
+			// [<p> <b> <i>  Enlarge</i> this</b>   </p>]
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_EnlargeI2' ).getFirst(), 2 );
+			range.setEnd( doc.getById( '_EnlargeB2' ), 3 );
+
+			range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
+
+			assert.areSame( document.getElementById( '_EnlargeP2' ).parentNode, range.startContainer.$, 'range.startContainer' );
+			assert.areSame( doc.getById( '_EnlargeP2' ).getIndex(), range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_EnlargeP2' ).parentNode, range.endContainer.$, 'range.endContainer' );
+			assert.areSame( doc.getById( '_EnlargeP2' ).getIndex() + 1, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_enlarge_element7_ie : function()
+		{
+			// IE normalizes whitespaces when parsing the HTML, so we need a
+			// specific test for it.
+			if ( !CKEDITOR.env.ie )
+				return;
+
+			// <p><b><i>[Enlarge</i> this]</b></p>
+			// [<p><b><i>Enlarge</i> this</b></p>]
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_EnlargeI2' ).getFirst(), 0 );
+			range.setEnd( doc.getById( '_EnlargeB2' ), 2 );
+
+			range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
+
+			assert.areSame( document.getElementById( '_EnlargeP2' ).parentNode, range.startContainer.$, 'range.startContainer' );
+			assert.areSame( doc.getById( '_EnlargeP2' ).getIndex(), range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_EnlargeP2' ).parentNode, range.endContainer.$, 'range.endContainer' );
+			assert.areSame( doc.getById( '_EnlargeP2' ).getIndex() + 1, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_enlarge_element8 : function()
+		{
+			// For IE, see the next test.
+			if ( CKEDITOR.env.ie )
+				return;
+
+			// <p> Test <b> <i>  [Enlarge</i> this</b>   </p><p> <b> <i>  Enlarge</i> this]</b>   </p>
+			// <p> Test [<b> <i>  Enlarge</i> this</b>   </p><p> <b> <i>  Enlarge</i> this</b>   </p>]
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_EnlargeI' ).getFirst(), 2 );
+			range.setEnd( doc.getById( '_EnlargeB2' ), 3 );
+
+			range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
+
+			assert.areSame( document.getElementById( '_EnlargeP' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_EnlargeP2' ).parentNode, range.endContainer.$, 'range.endContainer' );
+			assert.areSame( doc.getById( '_EnlargeP2' ).getIndex() + 1, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_enlarge_element8_ie : function()
+		{
+			// IE normalizes whitespaces when parsing the HTML, so we need a
+			// specific test for it.
+			if ( !CKEDITOR.env.ie )
+				return;
+
+			// <p>Test <b><i>[Enlarge</i> this</b></p><p><b><i>Enlarge</i> this]</b></p>
+			// <p>Test [<b><i>Enlarge</i> this</b></p><p><b><i>Enlarge</i> this</b></p>]
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_EnlargeI' ).getFirst(), 0 );
+			range.setEnd( doc.getById( '_EnlargeB2' ), 2 );
+
+			range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
+
+			assert.areSame( document.getElementById( '_EnlargeP' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_EnlargeP2' ).parentNode, range.endContainer.$, 'range.endContainer' );
+			assert.areSame( doc.getById( '_EnlargeP2' ).getIndex() + 1, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_enlarge_element9 : function()
+		{
+			// <p>Test<i> [Enlarge</i>]</p>
+			// <p>Test<i> [Enlarge</i>]</p>
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_EnlargeI3' ).getFirst(), 1 );
+			range.setEnd( doc.getById( '_EnlargeP3' ), 2 );
+
+			range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
+
+			assert.areSame( document.getElementById( '_EnlargeI3' ).firstChild, range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_EnlargeP3' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 2, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_enlarge_element10 : function()
+		{
+			// For IE, see the next test.
+			if ( CKEDITOR.env.ie )
+				return;
+
+			// <p>Test <i> [Enlarge</i>]</p>
+			// <p>Test [<i> Enlarge</i>]</p>
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_EnlargeI4' ).getFirst(), 1 );
+			range.setEnd( doc.getById( '_EnlargeP4' ), 2 );
+
+			range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
+
+			assert.areSame( document.getElementById( '_EnlargeP4' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_EnlargeP4' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 2, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_enlarge_element10_ie : function()
+		{
+			// IE normalizes whitespaces when parsing the HTML, so we need a
+			// specific test for it.
+			if ( !CKEDITOR.env.ie )
+				return;
+
+			// <p>Test <i>[Enlarge</i>]</p>
+			// <p>Test [<i>Enlarge</i>]</p>
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_EnlargeI4' ).getFirst(), 0 );
+			range.setEnd( doc.getById( '_EnlargeP4' ), 2 );
+
+			range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
+
+			assert.areSame( document.getElementById( '_EnlargeP4' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_EnlargeP4' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 2, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_enlarge_element11 : function()
+		{
+			// <p>Test <i>[Enlarge]</i></p>
+			// <p>Test [<i>Enlarge</i>]</p>
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_EnlargeI5' ), 0 );
+			range.setEnd( doc.getById( '_EnlargeI5' ), 1 );
+
+			range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
+
+			assert.areSame( document.getElementById( '_EnlargeP5' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_EnlargeP5' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 2, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_enlarge_element12 : function()
+		{
+			// <p>Test <i><b></b>[Enlarge]</i></p>
+			// <p>Test [<i><b></b>Enlarge</i>]</p>
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_EnlargeI6' ), 1 );
+			range.setEnd( doc.getById( '_EnlargeI6' ), 2 );
+
+			range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
+
+			assert.areSame( document.getElementById( '_EnlargeP6' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_EnlargeP6' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 2, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_deleteContents_W3C_1 : function()
+		{
+			// W3C DOM Range Specs - Section 2.6 - Example 1
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_Para' ).getFirst(), 1 );
+			range.setEnd( doc.getById( '_Para' ), 2 );
+
+			range.deleteContents();
+
+			assert.areSame( 't text.', getInnerHtml( '_Para' ), 'HTML after deletion' );
+
+			assert.areSame( document.getElementById( '_Para' ).firstChild, range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_Para' ).firstChild, range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 1, range.endOffset, 'range.endOffset' );
+			assert.isTrue( range.collapsed, 'range.collapsed' );
+		},
+
+		test_deleteContents_W3C_2 : function()
+		{
+			// W3C DOM Range Specs - Section 2.6 - Example 2
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_B' ).getFirst(), 1 );
+			range.setEnd( doc.getById( '_B' ).getNext(), 1 );
+
+			range.deleteContents();
+
+			assert.areSame( getInnerHtml( '_Para' ), 'this is <b id=_b>s</b>text.' );
+
+			assert.areSame( document.getElementById( '_Para' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 2, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_Para' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 2, range.endOffset, 'range.endOffset' );
+			assert.isTrue( range.collapsed, 'range.collapsed' );
+		},
+
+		test_deleteContents_W3C_3 : function()
+		{
+			// W3C DOM Range Specs - Section 2.6 - Example 3
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_B' ).getPrevious(), 1 );
+			range.setEnd( doc.getById( '_B' ).getFirst(), 1 );
+
+			range.deleteContents();
+
+			assert.areSame( 't<b id=_b>ome</b> text.', getInnerHtml( '_Para' ) );
+
+			assert.areSame( document.getElementById( '_Para' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_Para' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 1, range.endOffset, 'range.endOffset' );
+			assert.isTrue( range.collapsed, 'range.collapsed' );
+		},
+
+		test_deleteContents_W3C_4 : function()
+		{
+			// W3C DOM Range Specs - Section 2.6 - Example 4
+
+			var range = new CKEDITOR.dom.range( document );
+			range.setStart( doc.getById( '_H1' ).getFirst(), 1 );
+			range.setEnd( doc.getById( 'playground' ).getLast().getFirst(), 1 );
+
+			range.deleteContents();
+
+			assert.areSame( '<h1 id=_h1>f</h1><p>nother paragraph.</p>', getInnerHtml( 'playground' ) );
+
+			assert.areSame( document.getElementById( 'playground' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( 'playground' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 1, range.endOffset, 'range.endOffset' );
+			assert.isTrue( range.collapsed, 'range.collapsed' );
+		},
+
+		test_deleteContents_Other : function()
+		{
+			var range = new CKEDITOR.dom.range( doc );
+			range.setStart( doc.getById( '_H1' ), 0 );
+			range.setEnd( doc.getById( 'playground' ).getLast(), 1 );
+
+			range.deleteContents();
+
+			assert.areSame( '<h1 id=_h1></h1><p></p>', getInnerHtml( 'playground' ) );
+
+			assert.areSame( document.getElementById( 'playground' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( 'playground' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 1, range.endOffset, 'range.endOffset' );
+			assert.isTrue( range.collapsed, 'range.collapsed' );
+		},
+
+		test_deleteContents_Other_2 : function()
+		{
+			var range = new CKEDITOR.dom.range( doc );
+			range.setStart( doc.getById( 'playground' ), 0 );
+			range.setEnd( doc.getById( 'playground' ), 2 );
+
+			range.deleteContents();
+
+			assert.areSame( '<p>another paragraph.</p>', getInnerHtml( 'playground' ) );
+
+			assert.areSame( document.getElementById( 'playground' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame(document.getElementById( 'playground' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 0, range.endOffset, 'range.endOffset' );
+			assert.isTrue( range.collapsed, 'range.collapsed' );
+		},
+
+		test_deleteContents_Other_3 : function()
+		{
+			var range = new CKEDITOR.dom.range( doc );
+			range.selectNodeContents( doc.getById('_B') );
+
+			range.deleteContents();
+
+			assert.areSame( '', getInnerHtml('_B') );
+
+			assert.areSame( document.getElementById('_B'), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById('_B'), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 0, range.endOffset, 'range.endOffset' );
+			assert.isTrue( range.collapsed, 'range.collapsed' );
+		},
+
+		test_deleteContents_Other_4 : function()
+		{
+			var range = new CKEDITOR.dom.range( doc );
+			range.selectNodeContents( doc.getById('_Para') );
+
+			range.deleteContents();
+
+			assert.areSame( '', getInnerHtml('_Para') );
+
+			assert.areSame( document.getElementById('_Para'), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById('_Para'), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 0, range.endOffset, 'range.endOffset' );
+			assert.isTrue( range.collapsed, 'range.collapsed' );
+		},
+
+		test_extractContents_W3C_1 : function()
+		{
+			// W3C DOM Range Specs - Section 2.7 - Example 1
+
+			var range = new CKEDITOR.dom.range( doc );
+			range.setStart( doc.getById( '_Para' ).getFirst(), 1 );
+			range.setEnd( doc.getById( '_Para' ), 2 );
+
+			var docFrag = range.extractContents();
+
+			var tmpDiv = doc.createElement( 'div' );
+			docFrag.appendTo( tmpDiv );
+
+			assert.areSame( 'his is <b id=_b>some</b>', getInnerHtml( tmpDiv.$ ), 'Extracted HTML' );
+			assert.areSame( 't text.', getInnerHtml( '_Para' ), 'HTML after extraction' );
+
+			assert.areSame( document.getElementById( '_Para' ).firstChild, range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_Para' ).firstChild, range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 1, range.endOffset, 'range.endOffset' );
+			assert.isTrue( range.collapsed, 'range.collapsed' );
+		},
+
+		test_extractContents_W3C_2 : function()
+		{
+			// W3C DOM Range Specs - Section 2.7 - Example 2
+
+			var range = new CKEDITOR.dom.range( doc );
+			range.setStart( doc.getById( '_B' ).getFirst(), 1 );
+			range.setEnd( doc.getById( '_B' ).getNext(), 2 );
+
+			var docFrag = range.extractContents();
+
+			var tmpDiv = doc.createElement( 'div' );
+			docFrag.appendTo( tmpDiv );
+
+			assert.areSame( '<b id=_b>ome</b> t', getInnerHtml( tmpDiv.$ ), 'Extracted HTML' );
+			assert.areSame( 'this is <b id=_b>s</b>ext.', getInnerHtml( '_Para' ), 'HTML after extraction' );
+
+			assert.areSame( document.getElementById( '_Para' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 2, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_Para' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 2, range.endOffset, 'range.endOffset' );
+			assert.isTrue( range.collapsed, 'range.collapsed' );
+		},
+
+		test_extractContents_W3C_3 : function()
+		{
+			// W3C DOM Range Specs - Section 2.6 - Example 3
+
+			var range = new CKEDITOR.dom.range( doc );
+			range.setStart( doc.getById( '_B' ).getPrevious(), 1 );
+			range.setEnd( doc.getById( '_B' ).getFirst(), 1 );
+
+			var docFrag = range.extractContents();
+
+			var tmpDiv = doc.createElement( 'div' );
+			docFrag.appendTo( tmpDiv );
+
+			assert.areSame( 'his is <b id=_b>s</b>', getInnerHtml( tmpDiv.$ ), 'Extracted HTML' );
+			assert.areSame( 't<b id=_b>ome</b> text.', getInnerHtml( '_Para' ), 'HTML after extraction' );
+
+			assert.areSame( document.getElementById( '_Para' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_Para' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 1, range.endOffset, 'range.endOffset' );
+			assert.isTrue( range.collapsed, 'range.collapsed' );
+		},
+
+		test_extractContents_W3C_4 : function()
+		{
+			// W3C DOM Range Specs - Section 2.6 - Example 4
+
+			var range = new CKEDITOR.dom.range( doc );
+			range.setStart( doc.getById( '_H1' ).getFirst(), 1 );
+			range.setEnd( doc.getById( 'playground' ).getLast().getFirst(), 1 );
+
+			var docFrag = range.extractContents();
+
+			var tmpDiv = doc.createElement( 'div' );
+			docFrag.appendTo( tmpDiv );
+
+			assert.areSame( '<h1 id=_h1>ckw3crange test</h1><p id=_para>this is <b id=_b>some</b> text.</p><p>a</p>', getInnerHtml( tmpDiv.$ ), 'Extracted HTML' );
+			assert.areSame( '<h1 id=_h1>f</h1><p>nother paragraph.</p>', getInnerHtml( 'playground' ) );
+
+			assert.areSame( document.getElementById( 'playground' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( 'playground' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 1, range.endOffset, 'range.endOffset' );
+			assert.isTrue( range.collapsed, 'range.collapsed' );
+		},
+
+		test_extractContents_Other : function()
+		{
+			var range = new CKEDITOR.dom.range( doc );
+			range.setStart( doc.getById( '_H1' ), 0 );
+			range.setEnd( doc.getById( 'playground' ).getLast(), 1 );
+
+			var docFrag = range.extractContents();
+
+			var tmpDiv = doc.createElement( 'div' );
+			docFrag.appendTo( tmpDiv );
+
+			assert.areSame( '<h1 id=_h1>fckw3crange test</h1><p id=_para>this is <b id=_b>some</b> text.</p><p>another paragraph.</p>', getInnerHtml( tmpDiv.$ ), 'Extracted HTML' );
+			assert.areSame( '<h1 id=_h1></h1><p></p>', getInnerHtml( 'playground' ) );
+
+			assert.areSame( document.getElementById( 'playground' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( 'playground' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 1, range.endOffset, 'range.endOffset' );
+			assert.isTrue( range.collapsed, 'range.collapsed' );
+		},
+
+		test_extractContents_Other_2 : function()
+		{
+			var range = new CKEDITOR.dom.range( doc );
+			range.setStart( doc.getById( 'playground' ), 0 );
+			range.setEnd( doc.getById( 'playground' ), 2 );
+
+			var docFrag = range.extractContents();
+
+			var tmpDiv = doc.createElement( 'div' );
+			docFrag.appendTo( tmpDiv );
+
+			assert.areSame( '<h1 id=_h1>fckw3crange test</h1><p id=_para>this is <b id=_b>some</b> text.</p>', getInnerHtml( tmpDiv.$ ), 'Extracted HTML' );
+			assert.areSame( '<p>another paragraph.</p>', getInnerHtml( 'playground' ) );
+
+			assert.areSame( document.getElementById( 'playground' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( 'playground' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 0, range.endOffset, 'range.endOffset' );
+			assert.isTrue( range.collapsed, 'range.collapsed' );
+		},
+
+		test_extractContents_Other_3 : function()
+		{
+			var range = new CKEDITOR.dom.range( doc );
+
+			range.selectNodeContents( doc.getById('_B') );
+
+			var docFrag = range.extractContents();
+
+			var tmpDiv = doc.createElement( 'div' );
+			docFrag.appendTo( tmpDiv );
+
+			assert.areSame( 'some', getInnerHtml( tmpDiv.$ ), 'Extracted HTML' );
+			assert.areSame( '', getInnerHtml('_B'), 'HTML after extraction' );
+
+			assert.areSame( document.getElementById('_B'), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById('_B'), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 0, range.endOffset, 'range.endOffset' );
+			assert.isTrue( range.collapsed, 'range.collapsed' );
+		},
+
+		test_extractContents_Other_4 : function()
+		{
+			var range = new CKEDITOR.dom.range( doc );
+
+			range.selectNodeContents( doc.getById('_Para') );
+
+			var docFrag = range.extractContents();
+
+			var tmpDiv = doc.createElement( 'div' );
+			docFrag.appendTo( tmpDiv );
+
+			assert.areSame( 'this is <b id=_b>some</b> text.', getInnerHtml( tmpDiv.$ ), 'Extracted HTML' );
+			assert.areSame( '', getInnerHtml('_Para'), 'HTML after extraction' );
+
+			assert.areSame( document.getElementById('_Para'), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById('_Para'), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 0, range.endOffset, 'range.endOffset' );
+			assert.isTrue( range.collapsed, 'range.collapsed' );
+		},
+
+		test_extractContents_Other_5 : function()
+		{
+			document.getElementById( 'playground' ).innerHTML = '<p><b><i>test</i></b></p>';
+
+			var range = new CKEDITOR.dom.range( doc );
+			range.setStartAfter( new CKEDITOR.dom.element( document.getElementById( 'playground' ).getElementsByTagName('i')[0] ) );
+			range.setEndAfter( new CKEDITOR.dom.element( document.getElementById( 'playground' ).getElementsByTagName('b')[0] ) );
+
+			var docFrag = range.extractContents();
+
+			var tmpDiv = doc.createElement( 'div' );
+			docFrag.appendTo( tmpDiv );
+
+			assert.areSame( '<b></b>', getInnerHtml( tmpDiv.$ ), 'Extracted HTML' );
+			assert.areSame( '<p><b><i>test</i></b></p>', getInnerHtml( 'playground' ), 'HTML after extraction' );
+
+			assert.areSame( document.getElementById( 'playground' ).firstChild, range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( 'playground' ).firstChild, range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 1, range.endOffset, 'range.endOffset' );
+			assert.isTrue( range.collapsed, 'range.collapsed' );
+		},
+
+		test_extractContents_Other_6 : function()
+		{
+			document.getElementById( 'playground' ).innerHTML = '<p><b><i>test</i></b></p>';
+
+			var range = new CKEDITOR.dom.range( doc );
+
+			range.setStartBefore( new CKEDITOR.dom.element( document.getElementById( 'playground' ).getElementsByTagName('b')[0] ) );
+			range.setEndBefore( new CKEDITOR.dom.element( document.getElementById( 'playground' ).getElementsByTagName('i')[0] ) );
+
+			var docFrag = range.extractContents();
+
+			var tmpDiv = doc.createElement( 'div' );
+			docFrag.appendTo( tmpDiv );
+
+			assert.areSame( '<b></b>', getInnerHtml( tmpDiv.$ ), 'Extracted HTML' );
+			assert.areSame( '<p><b><i>test</i></b></p>', getInnerHtml( 'playground' ), 'HTML after extraction' );
+
+			assert.areSame( document.getElementById( 'playground' ).firstChild, range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( 'playground' ).firstChild, range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 0, range.endOffset, 'range.endOffset' );
+			assert.isTrue( range.collapsed, 'range.collapsed' );
+		},
+
+		test_cloneContents_W3C_1 : function()
+		{
+			// W3C DOM Range Specs - Section 2.7 - Example 1
+
+			var range = new CKEDITOR.dom.range( doc );
+			range.setStart( doc.getById( '_Para' ).getFirst(), 1 );
+			range.setEnd( doc.getById( '_Para' ), 2 );
+
+			var bodyHtml = document.getElementById( 'playground' ).innerHTML;
+
+			var docFrag = range.cloneContents();
+
+			var tmpDiv = doc.createElement( 'div' );
+			docFrag.appendTo( tmpDiv );
+
+			assert.areSame( 'his is <b id=_b>some</b>', getInnerHtml( tmpDiv.$ ), 'Cloned HTML' );
+
+			// The body HTML must remain unchanged.
+			assert.areSame( bodyHtml.replace( /\s+_cke_expando=["\d]+/g, '' ), document.getElementById( 'playground' ).innerHTML.replace( /\s+_cke_expando=["\d]+/g, '' ), 'The HTML must remain untouched' );
+
+			// The range must also remain unchanged.
+			assert.areSame( document.getElementById( '_Para' ).firstChild, range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_Para' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 2, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_cloneContents_W3C_2 : function()
+		{
+			// W3C DOM Range Specs - Section 2.7 - Example 2
+
+			var range = new CKEDITOR.dom.range( doc );
+			range.setStart( doc.getById( '_B' ).getFirst(), 1 );
+			range.setEnd( doc.getById( '_B' ).getNext(), 2 );
+
+			var bodyHtml = document.getElementById( 'playground' ).innerHTML;
+
+			var docFrag = range.cloneContents();
+
+			var tmpDiv = doc.createElement( 'div' );
+			docFrag.appendTo( tmpDiv );
+
+			assert.areSame( '<b id=_b>ome</b> t', getInnerHtml( tmpDiv.$ ), 'Cloned HTML' );
+
+			// The body HTML must remain unchanged.
+			assert.areSame( bodyHtml.replace( /\s+_cke_expando=["\d]+/g, '' ), document.getElementById( 'playground' ).innerHTML.replace( /\s+_cke_expando=["\d]+/g, '' ), 'The HTML must remain untouched' );
+
+			// The range must also remain unchanged.
+			assert.areSame( document.getElementById( '_B' ).firstChild, range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_B' ).nextSibling, range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 2, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_cloneContents_W3C_3 : function()
+		{
+			// W3C DOM Range Specs - Section 2.6 - Example 3
+
+			var range = new CKEDITOR.dom.range( doc );
+			range.setStart( doc.getById( '_B' ).getPrevious(), 1 );
+			range.setEnd( doc.getById( '_B' ).getFirst(), 1 );
+
+			var bodyHtml = document.getElementById( 'playground' ).innerHTML;
+
+			var docFrag = range.cloneContents();
+
+			var tmpDiv = doc.createElement( 'div' );
+			docFrag.appendTo( tmpDiv );
+
+			assert.areSame( 'his is <b id=_b>s</b>', getInnerHtml( tmpDiv.$ ), 'Cloned HTML' );
+
+			// The body HTML must remain unchanged.
+			assert.areSame( bodyHtml.replace( /\s+_cke_expando=["\d]+/g, '' ), document.getElementById( 'playground' ).innerHTML.replace( /\s+_cke_expando=["\d]+/g, '' ), 'The HTML must remain untouched' );
+
+			// The range must also remain unchanged.
+			assert.areSame( document.getElementById( '_B' ).previousSibling, range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( '_B' ).firstChild, range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 1, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		// W3C DOM Range Specs - Section 2.6 - Example 4
+		test_cloneContents_W3C_4 : function()
+		{
+			var range = new CKEDITOR.dom.range( doc );
+
+			range.setStart( doc.getById( '_H1' ).getFirst(), 1 );
+			range.setEnd( doc.getById( 'playground' ).getLast().getFirst(), 1 );
+
+			var bodyHtml = document.getElementById( 'playground' ).innerHTML;
+
+			var docFrag = range.cloneContents();
+
+			var tmpDiv = doc.createElement( 'div' );
+			docFrag.appendTo( tmpDiv );
+
+			assert.areSame( '<h1 id=_h1>ckw3crange test</h1><p id=_para>this is <b id=_b>some</b> text.</p><p>a</p>', getInnerHtml( tmpDiv.$ ), 'Cloned HTML' );
+
+			// The body HTML must remain unchanged.
+			assert.areSame( bodyHtml.replace( /\s+_cke_expando=["\d]+/g, '' ), document.getElementById( 'playground' ).innerHTML.replace( /\s+_cke_expando=["\d]+/g, '' ), 'The HTML must remain untouched' );
+
+			// The range must also remain unchanged.
+			assert.areSame( document.getElementById( '_H1' ).firstChild, range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( 'playground' ).lastChild.firstChild, range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 1, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_cloneContents_Other : function()
+		{
+			var range = new CKEDITOR.dom.range( doc );
+
+			range.setStart( doc.getById( '_H1' ), 0 );
+			range.setEnd( doc.getById( 'playground' ).getLast(), 1 );
+
+			var bodyHtml = document.getElementById( 'playground' ).innerHTML;
+
+			var docFrag = range.cloneContents();
+
+			var tmpDiv = doc.createElement( 'div' );
+			docFrag.appendTo( tmpDiv );
+
+			assert.areSame( '<h1 id=_h1>fckw3crange test</h1><p id=_para>this is <b id=_b>some</b> text.</p><p>another paragraph.</p>', getInnerHtml( tmpDiv.$ ), 'Cloned HTML' );
+
+			// The body HTML must remain unchanged.
+			assert.areSame( bodyHtml.replace( /\s+_cke_expando=["\d]+/g, '' ), document.getElementById( 'playground' ).innerHTML.replace( /\s+_cke_expando=["\d]+/g, '' ), 'The HTML must remain untouched' );
+
+			// The range must also remain unchanged.
+			assert.areSame( document.getElementById( '_H1' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( 'playground' ).lastChild, range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 1, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_cloneContents_Other_2 : function()
+		{
+			var range = new CKEDITOR.dom.range( doc );
+
+			range.setStart( doc.getById( 'playground' ), 0 );
+			range.setEnd( doc.getById( 'playground' ), 2 );
+
+			var bodyHtml = document.getElementById( 'playground' ).innerHTML;
+
+			var docFrag = range.cloneContents();
+
+			var tmpDiv = doc.createElement( 'div' );
+			docFrag.appendTo( tmpDiv );
+
+			assert.areSame( '<h1 id=_h1>fckw3crange test</h1><p id=_para>this is <b id=_b>some</b> text.</p>', getInnerHtml( tmpDiv.$ ), 'Cloned HTML' );
+
+			// The body HTML must remain unchanged.
+			assert.areSame( bodyHtml.replace( /\s+_cke_expando=["\d]+/g, '' ), document.getElementById( 'playground' ).innerHTML.replace( /\s+_cke_expando=["\d]+/g, '' ), 'The HTML must remain untouched' );
+
+			// The range must also remain unchanged.
+			assert.areSame( document.getElementById( 'playground' ), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById( 'playground' ), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 2, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_cloneContents_Other_3 : function()
+		{
+			var range = new CKEDITOR.dom.range( doc );
+
+			range.selectNodeContents( doc.getById('_B') );
+
+			var bodyHtml = document.getElementById( 'playground' ).innerHTML;
+
+			var docFrag = range.cloneContents();
+
+			var tmpDiv = doc.createElement( 'div' );
+			docFrag.appendTo( tmpDiv );
+
+			assert.areSame( 'some', getInnerHtml( tmpDiv.$ ), 'Cloned HTML' );
+
+			// The body HTML must remain unchanged.
+			assert.areSame( bodyHtml.replace( /\s+_cke_expando=["\d]+/g, '' ), document.getElementById( 'playground' ).innerHTML.replace( /\s+_cke_expando=["\d]+/g, '' ), 'The HTML must remain untouched' );
+
+			assert.areSame( document.getElementById('_B'), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById('_B'), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 1, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		test_cloneContents_Other_4 : function()
+		{
+			var range = new CKEDITOR.dom.range( doc );
+
+			range.selectNodeContents( doc.getById('_Para') );
+
+			var bodyHtml = document.getElementById( 'playground' ).innerHTML;
+
+			var docFrag = range.cloneContents();
+
+			var tmpDiv = doc.createElement( 'div' );
+			docFrag.appendTo( tmpDiv );
+
+			assert.areSame( 'this is <b id=_b>some</b> text.', getInnerHtml( tmpDiv.$ ), 'Cloned HTML' );
+
+			// The body HTML must remain unchanged.
+			assert.areSame( bodyHtml.replace( /\s+_cke_expando=["\d]+/g, '' ), document.getElementById( 'playground' ).innerHTML.replace( /\s+_cke_expando=["\d]+/g, '' ), 'The HTML must remain untouched' );
+
+			assert.areSame( document.getElementById('_Para'), range.startContainer.$, 'range.startContainer' );
+			assert.areSame( 0, range.startOffset, 'range.startOffset' );
+			assert.areSame( document.getElementById('_Para'), range.endContainer.$, 'range.endContainer' );
+			assert.areSame( 3, range.endOffset, 'range.endOffset' );
+			assert.isFalse( range.collapsed, 'range.collapsed' );
+		},
+
+		/////////////
+
+		setUp : function()
+		{
+			 document.getElementById( 'playground' ).innerHTML = html1;
+			 document.getElementById( 'playground2' ).innerHTML = html2;
+		},
+
+		name : document.title
+	};
+})() );
+
+//window.onload = function()
+//{
+//	// Local references.
+//	var assert			= CKEDITOR.test.assert;
+//	var getInnerHtml	= CKEDITOR.test.getInnerHtml;
+
+//	var doc = new CKEDITOR.dom.document( document );
+
+//			// W3C DOM Range Specs - Section 2.7 - Example 1
+
+//			var range = new CKEDITOR.dom.range( doc );
+//			range.setStart( doc.getById( '_Para' ).getFirst(), 1 );
+//			range.setEnd( doc.getById( '_Para' ), 2 );
+
+//			var docFrag = range.extractContents();
+
+//			var tmpDiv = doc.createElement( 'div' );
+//			docFrag.appendTo( tmpDiv );
+
+//			assert.areSame( 'his is <b id=_b>some</b>', getInnerHtml( tmpDiv.$ ), 'Extracted HTML' );
+//			assert.areSame( 't text.', getInnerHtml( '_Para' ), 'HTML after extraction' );
+
+//			assert.areSame( document.getElementById( '_Para' ).firstChild, range.startContainer.$, 'range.startContainer' );
+//			assert.areSame( 1, range.startOffset, 'range.startOffset' );
+//			assert.areSame( document.getElementById( '_Para' ).firstChild, range.endContainer.$, 'range.endContainer' );
+//			assert.areSame( 1, range.endOffset, 'range.endOffset' );
+//			assert.isTrue( range.collapsed, 'range.collapsed' );
+//}
+
+	//]]>
+	</script>
+</head>
+<body>
+	<div id="playground" style="visibility:hidden"><h1 id="_H1">FCKW3CRange Test</h1><p id="_Para">This is <b id="_B">some</b> text.</p><p>Another paragraph.</p></div>
+	<div id="playground2" style="visibility: hidden">
+		<h1>
+			Test page for FCKeditor
+		</h1>
+		<p id="_P">
+			This document contains various markup features commonly used by content editors
+			or "<span id="_Span" lang="fr">r&eacute;dacteurs de contenu</span>" as they are
+			called in <a id="_A" href="http://en.wikipedia.org/wiki/France" title="Wikipedia article about France">
+				France</a>.<br />
+			It is important that a <acronym id="_Acronym" title="what you see is what you get">WYSIWYG</acronym>
+			tool has features that are easily available for the editor. If not, there is a risk
+			that content won't receive <strong id="_Strong">proper</strong> markup. Examples
+			of commonly found content are:</p>
+		<p id="_Pnbsp">
+			&nbsp;
+		</p>
+		<p id="_Pspaces">
+		</p>
+		<ol>
+			<li>Headings</li>
+			<li style="color: Red">Links (with optional title) </li>
+			<li>Lists (like this one)
+				<ul>
+					<li>including nested lists </li>
+				</ul>
+			</li>
+			<li>Tables
+				<ul>
+					<li>caption</li>
+					<li>headers</li>
+					<li>summary</li>
+				</ul>
+			</li>
+			<li>Language information</li>
+			<li>Acronyms and abbreviations</li>
+			<li>Emphasis and strong emphasis </li>
+			<li>Quotes, inline and block </li>
+			<li>Images</li>
+		</ol>
+		<hr />
+		<h2 style="background-color: Silver">
+			Test procedure
+		</h2>
+		This text has no block tag. It should be corrected when working with the enter key
+		set to "p" or "div" tags. The "br" configuration should not make changes instead.
+		<p id="_P2">
+			In the test we will try to recreate this document using the editor tools. To make
+			sure tables can be inserted <em id="_Em">properly</em> we re-visit banana import
+			statistics from 1998.
+		</p>
+		<p id="_P3">
+			This paragraph has and image at the very end of its contents.<img id="_Img" src="img.gif"
+				alt="" />
+		</p>
+		This text has no block tag.<br />It should be corrected when working with the enter key
+		set to "p" or "div" tags. The <strong id="_StrongNoPara">"br" configuration</strong>
+		should not make changes instead.<br />It has three lines separated by BR tags.
+		<p>
+			In the test we will try to recreate this document using the editor tools. To make
+			sure tables can be inserted <em>properly</em> we re-visit banana import statistics
+			from 1998.
+		</p>
+		<table summary="Sweden was the top importing country by far in 1998.">
+			<caption>
+				Top banana importers 1998 (value of banana imports in millions of US dollars per
+				million people)<br />
+				<br />
+			</caption>
+			<tr>
+				<th scope="col">
+					Country</th>
+				<th scope="col">
+					Millions of US dollars per million people</th>
+			</tr>
+			<tr>
+				<td>
+					Sweden</td>
+				<td>
+					17.12</td>
+			</tr>
+			<tr>
+				<td>
+					United&nbsp;Kingdom</td>
+				<td>
+					8.88</td>
+			</tr>
+			<tr>
+				<td>
+					Germany</td>
+				<td>
+					8.36</td>
+			</tr>
+			<tr>
+				<td>
+					Italy</td>
+				<td>
+					5.96</td>
+			</tr>
+			<tr>
+				<td>
+					United States</td>
+				<td>
+					4.78</td>
+			</tr>
+		</table>
+		<p>
+			For block quotes we will have a look at <a href="http://fawny.org/rhcp.html">what Joe
+				Clark says about redheads</a>:</p>
+		<blockquote cite="http://fawny.org/rhcp.html#me">
+			<p>
+				"Since boyhood I&rsquo;ve always believed, at the deepest level, that redheads are
+				standard-bearers of the grandest and most wondrous human beauty."</p>
+		</blockquote>
+		<p>
+			<img src="img.gif" alt="" /></p>
+		<p>
+			The above is the FCKeditor logo loaded from the FCKeditor.net web site.</p>
+		<p><b id="_B1">Line 1<br />Line 2<br /><i id="_I1">Line 3</i></b></p>
+		<p id="_EnlargeP"> Test <b id="_EnlargeB"> <i id="_EnlargeI">  Enlarge</i> this</b>   </p>
+		<p id="_EnlargeP1"> Test<b id="_EnlargeB1"> <i id="_EnlargeI1">  Enlarge</i> this</b>   </p>
+		<p id="_EnlargeP2"> <b id="_EnlargeB2"> <i id="_EnlargeI2">  Enlarge</i> this</b>   </p>
+		<p id="_EnlargeP3">Test<i id="_EnlargeI3"> Enlarge</i></p>
+		<p id="_EnlargeP4">Test <i id="_EnlargeI4"> Enlarge</i></p>
+		<p id="_EnlargeP5">Test <i id="_EnlargeI5">Enlarge</i></p>
+		<p id="_EnlargeP6">Test <i id="_EnlargeI6"><b></b>Enlarge</i></p>
+	</div>
+	<script type="text/javascript">
+	//<![CDATA[
+
+html1 = document.getElementById( 'playground' ).innerHTML;
+html2 = document.getElementById( 'playground2' ).innerHTML;
+
+	//]]>
+	</script>
+</body>
+</html>
Index: /CKEditor/branches/prototype/_source/tests/core/dom/text.html
===================================================================
--- /CKEditor/branches/prototype/_source/tests/core/dom/text.html	(revision 2573)
+++ /CKEditor/branches/prototype/_source/tests/core/dom/text.html	(revision 2573)
@@ -0,0 +1,69 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>CKEDITOR.dom.text</title>
+	<link rel="stylesheet" type="text/css" href="../../test.css" />
+	<script type="text/javascript" src="../../../../ckeditor_source.js"></script>
+	<script type="text/javascript" src="../../test.js"></script>
+	<script type="text/javascript">
+	//<![CDATA[
+
+CKEDITOR.test.addTestCase( (function()
+{
+	// Local reference to the "assert" object.
+	var assert = CKEDITOR.test.assert;
+
+	return {
+		test_substring1 : function()
+		{
+			var text = new CKEDITOR.dom.text( '0123456789' );
+			
+			assert.areSame( '123', text.substring( 1, 4 ) );
+		},
+
+		test_substring2 : function()
+		{
+			var text = new CKEDITOR.dom.text( '0123456789' );
+			
+			assert.areSame( '56789', text.substring( 5 ) );
+		},
+
+		test_substring3 : function()
+		{
+			var text = new CKEDITOR.dom.text( '0123456789' );
+			
+			assert.areSame( '', text.substring( 1,1 ) );
+		},
+
+		test_substring4 : function()
+		{
+			var text = new CKEDITOR.dom.text( '0123456789' );
+			
+			assert.areSame( '012', text.substring( -10,3 ) );
+		},
+
+		test_substring5 : function()
+		{
+			var text = new CKEDITOR.dom.text( '0123456789' );
+			
+			assert.areSame( '89', text.substring( 8,100 ) );
+		},
+
+		test_substring6 : function()
+		{
+			var text = new CKEDITOR.dom.text( '0123456789' );
+			
+			assert.areSame( '234', text.substring( 5,2 ) );
+		},
+
+		name : document.title
+	};
+})() );
+
+	//]]>
+	</script>
+</head>
+<body>
+	<p>0123456789</p>
+</body>
+</html>
Index: /CKEditor/branches/prototype/_source/tests/core/dom/window.html
===================================================================
--- /CKEditor/branches/prototype/_source/tests/core/dom/window.html	(revision 2572)
+++ /CKEditor/branches/prototype/_source/tests/core/dom/window.html	(revision 2573)
@@ -2,5 +2,5 @@
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
-	<title>CKEDITOR.dom.document</title>
+	<title>CKEDITOR.dom.window</title>
 	<link rel="stylesheet" type="text/css" href="../../test.css" />
 	<script type="text/javascript" src="../../../../ckeditor_source.js"></script>
Index: /CKEditor/branches/prototype/_source/tests/test.js
===================================================================
--- /CKEditor/branches/prototype/_source/tests/test.js	(revision 2572)
+++ /CKEditor/branches/prototype/_source/tests/test.js	(revision 2573)
@@ -71,5 +71,18 @@
 			{
 				case runner.TEST_FAIL_EVENT:
-					outputResult( '<span class="testFail">FAIL</span> Test named "' + data.testName + '" failed with message: "' + htmlEncode( data.error.message ) + '".<div>Expected:</div><pre>' + htmlEncode( data.error.expected ) + '<br></pre><div>Actual:</div><pre>' + htmlEncode( data.error.actual ) + '<br></pre>' );
+
+					var expected = data.error.expected;
+					if ( expected && expected.nodeType )
+						expected += ' (' + ( expected.nodeType == 1 ? expected.nodeName : expected.nodeValue ) + ')';
+					
+					var actual = data.error.actual;
+					if ( actual && actual.nodeType )
+						actual += ' (' + ( actual.nodeType == 1 ? actual.nodeName : actual.nodeValue ) + ')';
+					
+					outputResult( 
+						'<span class="testFail">FAIL</span> Test named "' + data.testName + 
+						'" failed with message: "' + htmlEncode( data.error.message ) + 
+						'".<div>Expected:</div><pre>' + htmlEncode( expected ) + 
+						'<br></pre><div>Actual:</div><pre>' + htmlEncode( actual ) + '<br></pre>' );
 					break;
 				case runner.TEST_PASS_EVENT:
Index: /CKEditor/branches/prototype/_source/tests/testall.html
===================================================================
--- /CKEditor/branches/prototype/_source/tests/testall.html	(revision 2572)
+++ /CKEditor/branches/prototype/_source/tests/testall.html	(revision 2573)
@@ -15,4 +15,6 @@
 	'core/dom/document',
 	'core/dom/element',
+	'core/dom/range',
+	'core/dom/window',
 	'core/env',
 	'core/event',
