Index: /CKEditor/branches/versions/3.4.x/CHANGES.html
===================================================================
--- /CKEditor/branches/versions/3.4.x/CHANGES.html	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/CHANGES.html	(revision 5709)
@@ -44,4 +44,5 @@
 		<li><a href="http://dev.fckeditor.net/ticket/5909">#5909</a> : BiDi: Support for switching base language.</li>
 		<li><a href="http://dev.fckeditor.net/ticket/4606">#4606</a> : Port 'AutoGrow' plugin from FCKEditor 2.x.</li>
+		<li><a href="http://dev.fckeditor.net/ticket/5737">#5737</a> : Added support for the <a href="http://www.w3.org/TR/html5/editing.html#contenteditable">HTML5 contenteditable attribute</a>, making it possible to define read only regions into the editor contents.</li>
 	</ul>
 	<p>
Index: /CKEditor/branches/versions/3.4.x/_source/core/dom/node.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/core/dom/node.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/core/dom/node.js	(revision 5709)
@@ -658,4 +658,25 @@
 				}
 			}
+		},
+
+		isReadOnly : function()
+		{
+			var current = this;
+			while( current )
+			{
+				if ( current.type == CKEDITOR.NODE_ELEMENT )
+				{
+					if ( current.is ( 'body' ) )
+						break;
+
+					if ( current.getAttribute( 'contentEditable' ) == 'false' )
+						return current;
+					else if ( current.getAttribute( 'contentEditable' ) == 'true' )
+						break;
+				}
+				current = current.getParent();
+			}
+
+			return false;
 		}
 	}
Index: /CKEditor/branches/versions/3.4.x/_source/core/dom/range.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/core/dom/range.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/core/dom/range.js	(revision 5709)
@@ -1,3 +1,3 @@
-/*
+﻿/*
 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
 For licensing, see LICENSE.html or http://ckeditor.com/license
Index: /CKEditor/branches/versions/3.4.x/_source/core/dom/rangelist.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/core/dom/rangelist.js	(revision 5709)
+++ /CKEditor/branches/versions/3.4.x/_source/core/dom/rangelist.js	(revision 5709)
@@ -0,0 +1,163 @@
+/*
+Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+
+(function()
+{
+	/**
+	 * Represents a list os CKEDITOR.dom.range objects, which can be easily
+	 * iterated sequentially.
+	 * @constructor
+	 * @param {CKEDITOR.dom.range|Array} [ranges] The ranges contained on this list.
+	 *		Note that, if an array of ranges is specified, the range sequence
+	 *		should match its DOM order. This class will not help to sort them.
+	 */
+	CKEDITOR.dom.rangeList = function( ranges )
+	{
+		if ( ranges.createIterator )
+			return ranges;
+
+		if ( !ranges )
+			ranges = [];
+		else if ( ranges instanceof CKEDITOR.dom.range )
+			ranges = [ ranges ];
+
+		return CKEDITOR.tools.extend( ranges, mixins );
+	};
+
+	var mixins =
+	/** @lends CKEDITOR.dom.rangeList.prototype */
+	{
+			/**
+			 * Creates an instance of the rangeList iterator, it should be used
+			 * only when the ranges processing could be DOM intrusive, which
+			 * means it may pollute and break other ranges in this list.
+			 * Otherwise, it's enough to just iterate over this array in a for loop.
+			 * @returns {CKEDITOR.dom.rangeListIterator}
+			 */
+			createIterator : function()
+			{
+				var rangeList = this,
+					bookmarks = [],
+					current;
+
+				/**
+				 * @lends CKEDITOR.dom.rangeListIterator.prototype
+				 */
+				return {
+
+					/**
+					 * Retrieves the next range in the list.
+					 */
+					getNextRange : function()
+					{
+						current = current == undefined ? 0 : current + 1;
+
+						var range = rangeList[ current ];
+
+						// Multiple ranges might be mangled by each other.
+						if ( range && rangeList.length > 1 )
+						{
+							// Bookmarking all other ranges on the first iteration,
+							// the range correctness after it doesn't matter since we'll
+							// restore them before the next iteration.
+							if ( current == 0 )
+							{
+								// Make sure bookmark correctness by reverse processing.
+								for ( var i = rangeList.length - 1; i > 0; i-- )
+									bookmarks.unshift( rangeList[ i ].createBookmark( true ) );
+							}
+							else
+								range.moveToBookmark( bookmarks.shift() );
+						}
+
+						return range;
+					}
+				};
+			},
+
+			createBookmarks : function( serializable )
+			{
+				var retval = [], bookmark;
+				for ( var i = 0; i < this.length ; i++ )
+				{
+					retval.push( bookmark = this[ i ].createBookmark( serializable, true) );
+
+					// Updating the container & offset values for ranges
+					// that have been touched.
+					for ( var j = i + 1; j < this.length; j++ )
+					{
+						this[ j ] = updateDirtyRange( bookmark, this[ j ] );
+						this[ j ] = updateDirtyRange( bookmark, this[ j ], true );
+					}
+				}
+				return retval;
+			},
+
+			createBookmarks2 : function( normalized )
+			{
+				var bookmarks = [];
+
+				for ( var i = 0 ; i < this.length ; i++ )
+					bookmarks.push( this[ i ].createBookmark2( normalized ) );
+
+				return bookmarks;
+			},
+
+			/**
+			 * Move each range in the list to the position specified by a list of bookmarks.
+			 * @param {Array} bookmarks The list of bookmarks, each one matching a range in the list.
+			 */
+			moveToBookmarks :  function( bookmarks )
+			{
+				for ( var i = 0 ; i < this.length ; i++ )
+					this[ i ].moveToBookmark( bookmarks[ i ] );
+			}
+	};
+
+	// Update the specified range which has been mangled by previous insertion of
+	// range bookmark nodes.(#3256)
+	function updateDirtyRange( bookmark, dirtyRange, checkEnd )
+	{
+		var serializable = bookmark.serializable,
+			container = dirtyRange[ checkEnd ? 'endContainer' : 'startContainer' ],
+			offset = checkEnd ? 'endOffset' : 'startOffset';
+
+		var bookmarkStart = serializable ?
+				dirtyRange.document.getById( bookmark.startNode )
+				: bookmark.startNode;
+
+		var bookmarkEnd = serializable ?
+				dirtyRange.document.getById( bookmark.endNode )
+				: bookmark.endNode;
+
+		if ( container.equals( bookmarkStart.getPrevious() ) )
+		{
+			dirtyRange.startOffset = dirtyRange.startOffset
+					- container.getLength()
+					- bookmarkEnd.getPrevious().getLength();
+			container = bookmarkEnd.getNext();
+		}
+		else if ( container.equals( bookmarkEnd.getPrevious() ) )
+		{
+			dirtyRange.startOffset = dirtyRange.startOffset - container.getLength();
+			container = bookmarkEnd.getNext();
+		}
+
+		container.equals( bookmarkStart.getParent() ) && dirtyRange[ offset ]++;
+		container.equals( bookmarkEnd.getParent() ) && dirtyRange[ offset ]++;
+
+		// Update and return this range.
+		dirtyRange[ checkEnd ? 'endContainer' : 'startContainer' ] = container;
+		return dirtyRange;
+	}
+})();
+
+/**
+ * (Virtual Class) Do not call this constructor. This class is not really part
+ *	of the API. It just describes the return type of {@link CKEDITOR.dom.rangeList#createIterator}.
+ * @name CKEDITOR.dom.rangeListIterator
+ * @constructor
+ * @example
+ */
Index: /CKEditor/branches/versions/3.4.x/_source/core/loader.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/core/loader.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/core/loader.js	(revision 5709)
@@ -1,3 +1,3 @@
-﻿/*
+/*
 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
 For licensing, see LICENSE.html or http://ckeditor.com/license
@@ -24,5 +24,5 @@
 		var scripts =
 		{
-			'core/_bootstrap'		: [ 'core/config', 'core/ckeditor', 'core/plugins', 'core/scriptloader', 'core/tools', /* The following are entries that we want to force loading at the end to avoid dependence recursion */ 'core/dom/comment', 'core/dom/elementpath', 'core/dom/text', 'core/dom/range' ],
+			'core/_bootstrap'		: [ 'core/config', 'core/ckeditor', 'core/plugins', 'core/scriptloader', 'core/tools', /* The following are entries that we want to force loading at the end to avoid dependence recursion */ 'core/dom/comment', 'core/dom/elementpath', 'core/dom/text', 'core/dom/rangelist' ],
 			'core/ajax'				: [ 'core/xml' ],
 			'core/ckeditor'			: [ '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/htmlparser/filter', 'core/htmlparser/basicwriter', 'core/tools' ],
@@ -42,4 +42,5 @@
 			'core/dom/domobject'	: [ 'core/dom/event' ],
 			'core/dom/range'		: [ 'core/dom/document', 'core/dom/documentfragment', 'core/dom/element', 'core/dom/walker' ],
+			'core/dom/rangelist'    : [ 'core/dom/range' ],
 			'core/dom/text'			: [ 'core/dom/node', 'core/dom/domobject' ],
 			'core/dom/walker'		: [ 'core/dom/node' ],
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/blockquote/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/blockquote/plugin.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/blockquote/plugin.js	(revision 5709)
@@ -48,5 +48,5 @@
 			var state = editor.getCommand( 'blockquote' ).state,
 				selection = editor.getSelection(),
-				range = selection && selection.getRanges()[0];
+				range = selection && selection.getRanges( true )[0];
 
 			if ( !range )
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/clipboard/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/clipboard/plugin.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/clipboard/plugin.js	(revision 5709)
@@ -1,3 +1,3 @@
-﻿/*
+/*
 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
 For licensing, see LICENSE.html or http://ckeditor.com/license
@@ -387,12 +387,13 @@
 					}
 
-					editor.contextMenu.addListener( function()
+					editor.contextMenu.addListener( function( element, selection )
 						{
+							var readOnly = selection.getCommonAncestor().isReadOnly();
 							return {
-								cut : stateFromNamedCommand( 'Cut' ),
+								cut : !readOnly && stateFromNamedCommand( 'Cut' ),
 
 								// Browser bug: 'Cut' has the correct states for both Copy and Cut.
 								copy : stateFromNamedCommand( 'Cut' ),
-								paste : CKEDITOR.env.webkit ? CKEDITOR.TRISTATE_OFF : stateFromNamedCommand( 'Paste' )
+								paste : !readOnly && ( CKEDITOR.env.webkit ? CKEDITOR.TRISTATE_OFF : stateFromNamedCommand( 'Paste' ) )
 							};
 						});
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/div/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/div/plugin.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/div/plugin.js	(revision 5709)
@@ -96,5 +96,5 @@
 					editor.contextMenu.addListener( function( element, selection )
 						{
-							if ( !element )
+							if ( !element || element.isReadOnly())
 								return null;
 
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/enterkey/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/enterkey/plugin.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/enterkey/plugin.js	(revision 5709)
@@ -24,4 +24,9 @@
 			// Get the range for the current selection.
 			range = range || getRange( editor );
+
+			// We may not have valid ranges to work on, like when inside a
+			// contenteditable=false element.
+			if ( !range )
+				return;
 
 			var doc = range.document;
@@ -189,4 +194,9 @@
 			// Get the range for the current selection.
 			range = range || getRange( editor );
+
+			// We may not have valid ranges to work on, like when inside a
+			// contenteditable=false element.
+			if ( !range )
+				return;
 
 			var doc = range.document;
@@ -340,5 +350,5 @@
 	{
 		// Get the selection ranges.
-		var ranges = editor.getSelection().getRanges();
+		var ranges = editor.getSelection().getRanges( true );
 
 		// Delete the contents of all ranges except the first one.
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/find/dialogs/find.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/find/dialogs/find.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/find/dialogs/find.js	(revision 5709)
@@ -1,3 +1,3 @@
-﻿/*
+/*
 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
 For licensing, see LICENSE.html or http://ckeditor.com/license
@@ -6,7 +6,7 @@
 (function()
 {
-	function nonEmptyText( node )
+	function findEvaluator( node )
 	{
-		return ( node.type == CKEDITOR.NODE_TEXT && node.getLength() > 0 );
+		return node.type == CKEDITOR.NODE_TEXT && node.getLength() > 0 && !node.isReadOnly();
 	}
 
@@ -14,5 +14,5 @@
 	 * Elements which break characters been considered as sequence.
 	*/
-	function nonCharactersBoundary ( node )
+	function nonCharactersBoundary( node )
 	{
 		return !( node.type == CKEDITOR.NODE_ELEMENT && node.isBlockBoundary(
@@ -85,5 +85,5 @@
 				new CKEDITOR.dom.walker( range );
 			walker.guard = matchWord ? nonCharactersBoundary : null;
-			walker[ 'evaluator' ] = nonEmptyText;
+			walker[ 'evaluator' ] = findEvaluator;
 			walker.breakOnFalse = true;
 
@@ -252,6 +252,8 @@
 
 				// Apply the highlight.
-				var range = this.toDomRange();
+				var range = this.toDomRange(),
+					bookmark = range.createBookmark();
 				highlightStyle.applyToRange( range );
+				range.moveToBookmark( bookmark );
 				this._.highlightRange = range;
 
@@ -274,5 +276,7 @@
 					return;
 
+				var bookmark = this._.highlightRange.createBookmark();
 				highlightStyle.removeFromRange( this._.highlightRange );
+				this._.highlightRange.moveToBookmark( bookmark );
 				this.updateFromDomRange( this._.highlightRange );
 				this._.highlightRange = null;
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/flash/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/flash/plugin.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/flash/plugin.js	(revision 5709)
@@ -91,5 +91,6 @@
 				editor.contextMenu.addListener( function( element, selection )
 					{
-						if ( element && element.is( 'img' ) && element.getAttribute( '_cke_real_element_type' ) == 'flash' )
+						if ( element && element.is( 'img' ) && !element.isReadOnly()
+								&& element.getAttribute( '_cke_real_element_type' ) == 'flash' )
 							return { flash : CKEDITOR.TRISTATE_OFF };
 					});
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/forms/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/forms/plugin.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/forms/plugin.js	(revision 5709)
@@ -1,3 +1,3 @@
-﻿/*
+/*
 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
 For licensing, see LICENSE.html or http://ckeditor.com/license
@@ -132,5 +132,5 @@
 			editor.contextMenu.addListener( function( element )
 				{
-					if ( element && element.hasAscendant( 'form', true ) )
+					if ( element && element.hasAscendant( 'form', true ) && !element.isReadOnly() )
 						return { form : CKEDITOR.TRISTATE_OFF };
 				});
@@ -138,5 +138,5 @@
 			editor.contextMenu.addListener( function( element )
 				{
-					if ( element )
+					if ( element && !element.isReadOnly() )
 					{
 						var name = element.getName();
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/image/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/image/plugin.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/image/plugin.js	(revision 5709)
@@ -54,5 +54,5 @@
 			editor.contextMenu.addListener( function( element, selection )
 				{
-					if ( !element || !element.is( 'img' ) || element.getAttribute( '_cke_realelement' ) )
+					if ( !element || !element.is( 'img' ) || element.getAttribute( '_cke_realelement' ) || element.isReadOnly() )
 						return null;
 
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/indent/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/indent/plugin.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/indent/plugin.js	(revision 5709)
@@ -63,185 +63,4 @@
 	}
 
-	function indentList( editor, range, listNode )
-	{
-		// Our starting and ending points of the range might be inside some blocks under a list item...
-		// So before playing with the iterator, we need to expand the block to include the list items.
-		var startContainer = range.startContainer,
-			endContainer = range.endContainer;
-		while ( startContainer && !startContainer.getParent().equals( listNode ) )
-			startContainer = startContainer.getParent();
-		while ( endContainer && !endContainer.getParent().equals( listNode ) )
-			endContainer = endContainer.getParent();
-
-		if ( !startContainer || !endContainer )
-			return;
-
-		// Now we can iterate over the individual items on the same tree depth.
-		var block = startContainer,
-			itemsToMove = [],
-			stopFlag = false;
-		while ( !stopFlag )
-		{
-			if ( block.equals( endContainer ) )
-				stopFlag = true;
-			itemsToMove.push( block );
-			block = block.getNext();
-		}
-		if ( itemsToMove.length < 1 )
-			return;
-
-		// Do indent or outdent operations on the array model of the list, not the
-		// list's DOM tree itself. The array model demands that it knows as much as
-		// possible about the surrounding lists, we need to feed it the further
-		// ancestor node that is still a list.
-		var listParents = listNode.getParents( true );
-		for ( var i = 0 ; i < listParents.length ; i++ )
-		{
-			if ( listParents[i].getName && listNodeNames[ listParents[i].getName() ] )
-			{
-				listNode = listParents[i];
-				break;
-			}
-		}
-		var indentOffset = this.name == 'indent' ? 1 : -1,
-			startItem = itemsToMove[0],
-			lastItem = itemsToMove[ itemsToMove.length - 1 ],
-			database = {};
-
-		// Convert the list DOM tree into a one dimensional array.
-		var listArray = CKEDITOR.plugins.list.listToArray( listNode, database );
-
-		// Apply indenting or outdenting on the array.
-		var baseIndent = listArray[ lastItem.getCustomData( 'listarray_index' ) ].indent;
-		for ( i = startItem.getCustomData( 'listarray_index' ); i <= lastItem.getCustomData( 'listarray_index' ); i++ )
-		{
-			listArray[ i ].indent += indentOffset;
-			// Make sure the newly created sublist get a brand-new element of the same type. (#5372)
-			var listRoot = listArray[ i ].parent;
-			listArray[ i ].parent = new CKEDITOR.dom.element( listRoot.getName(), listRoot.getDocument() );
-		}
-
-		for ( i = lastItem.getCustomData( 'listarray_index' ) + 1 ;
-				i < listArray.length && listArray[i].indent > baseIndent ; i++ )
-			listArray[i].indent += indentOffset;
-
-		// Convert the array back to a DOM forest (yes we might have a few subtrees now).
-		// And replace the old list with the new forest.
-		var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode, 0 );
-
-		// Avoid nested <li> after outdent even they're visually same,
-		// recording them for later refactoring.(#3982)
-		if ( this.name == 'outdent' )
-		{
-			var parentLiElement;
-			if ( ( parentLiElement = listNode.getParent() ) && parentLiElement.is( 'li' ) )
-			{
-				var children = newList.listNode.getChildren(),
-					pendingLis = [],
-					count = children.count(),
-					child;
-
-				for ( i = count - 1 ; i >= 0 ; i-- )
-				{
-					if ( ( child = children.getItem( i ) ) && child.is && child.is( 'li' )  )
-						pendingLis.push( child );
-				}
-			}
-		}
-
-		if ( newList )
-			newList.listNode.replace( listNode );
-
-		// Move the nested <li> to be appeared after the parent.
-		if ( pendingLis && pendingLis.length )
-		{
-			for (  i = 0; i < pendingLis.length ; i++ )
-			{
-				var li = pendingLis[ i ],
-					followingList = li;
-
-				// Nest preceding <ul>/<ol> inside current <li> if any.
-				while ( ( followingList = followingList.getNext() ) &&
-					   followingList.is &&
-					   followingList.getName() in listNodeNames )
-				{
-					// IE requires a filler NBSP for nested list inside empty list item,
-					// otherwise the list item will be inaccessiable. (#4476)
-					if ( CKEDITOR.env.ie && !li.getFirst( function( node ){ return isNotWhitespaces( node ) && isNotBookmark( node ); } ) )
-						li.append( range.document.createText( '\u00a0' ) );
-
-					li.append( followingList );
-				}
-
-				li.insertAfter( parentLiElement );
-			}
-		}
-
-		// Clean up the markers.
-		CKEDITOR.dom.element.clearAllMarkers( database );
-	}
-
-	function indentBlock( editor, range )
-	{
-		var iterator = range.createIterator(),
-			enterMode = editor.config.enterMode;
-		iterator.enforceRealBlocks = true;
-		iterator.enlargeBr = enterMode != CKEDITOR.ENTER_BR;
-		var block;
-		while ( ( block = iterator.getNextParagraph() ) )
-			indentElement.call( this, editor, block );
-	}
-
-	function indentElement( editor, element )
-		{
-			if ( this.useIndentClasses )
-			{
-				// Transform current class name to indent step index.
-			var indentClass = element.$.className.match( this.classNameRegex ),
-					indentStep = 0;
-				if ( indentClass )
-				{
-					indentClass = indentClass[1];
-					indentStep = this.indentClassMap[ indentClass ];
-				}
-
-				// Operate on indent step index, transform indent step index back to class
-				// name.
-				if ( this.name == 'outdent' )
-					indentStep--;
-				else
-					indentStep++;
-
-			if ( indentStep < 0 )
-				return false;
-
-				indentStep = Math.min( indentStep, editor.config.indentClasses.length );
-				indentStep = Math.max( indentStep, 0 );
-			var className = CKEDITOR.tools.ltrim( element.$.className.replace( this.classNameRegex, '' ) );
-				if ( indentStep < 1 )
-				element.$.className = className;
-				else
-				element.addClass( editor.config.indentClasses[ indentStep - 1 ] );
-			}
-			else
-			{
-			var currentOffset = parseInt( element.getStyle( this.indentCssProperty ), 10 );
-				if ( isNaN( currentOffset ) )
-					currentOffset = 0;
-				currentOffset += ( this.name == 'indent' ? 1 : -1 ) * editor.config.indentOffset;
-
-			if ( currentOffset < 0 )
-				return false;
-
-				currentOffset = Math.max( currentOffset, 0 );
-				currentOffset = Math.ceil( currentOffset / editor.config.indentOffset ) * editor.config.indentOffset;
-			element.setStyle( this.indentCssProperty, currentOffset ? currentOffset + editor.config.indentUnit : '' );
-			if ( element.getAttribute( 'style' ) === '' )
-				element.removeAttribute( 'style' );
-			}
-
-		return true;
-		}
-
 	function indentCommand( editor, name )
 	{
@@ -268,53 +87,242 @@
 		exec : function( editor )
 		{
+			var self = this, database = {};
+
+			function indentList( listNode )
+			{
+				// Our starting and ending points of the range might be inside some blocks under a list item...
+				// So before playing with the iterator, we need to expand the block to include the list items.
+				var startContainer = range.startContainer,
+					endContainer = range.endContainer;
+				while ( startContainer && !startContainer.getParent().equals( listNode ) )
+					startContainer = startContainer.getParent();
+				while ( endContainer && !endContainer.getParent().equals( listNode ) )
+					endContainer = endContainer.getParent();
+
+				if ( !startContainer || !endContainer )
+					return;
+
+				// Now we can iterate over the individual items on the same tree depth.
+				var block = startContainer,
+					itemsToMove = [],
+					stopFlag = false;
+				while ( !stopFlag )
+				{
+					if ( block.equals( endContainer ) )
+						stopFlag = true;
+					itemsToMove.push( block );
+					block = block.getNext();
+				}
+				if ( itemsToMove.length < 1 )
+					return;
+
+				// Do indent or outdent operations on the array model of the list, not the
+				// list's DOM tree itself. The array model demands that it knows as much as
+				// possible about the surrounding lists, we need to feed it the further
+				// ancestor node that is still a list.
+				var listParents = listNode.getParents( true );
+				for ( var i = 0 ; i < listParents.length ; i++ )
+				{
+					if ( listParents[i].getName && listNodeNames[ listParents[i].getName() ] )
+					{
+						listNode = listParents[i];
+						break;
+					}
+				}
+				var indentOffset = self.name == 'indent' ? 1 : -1,
+					startItem = itemsToMove[0],
+					lastItem = itemsToMove[ itemsToMove.length - 1 ];
+
+				// Convert the list DOM tree into a one dimensional array.
+				var listArray = CKEDITOR.plugins.list.listToArray( listNode, database );
+
+				// Apply indenting or outdenting on the array.
+				var baseIndent = listArray[ lastItem.getCustomData( 'listarray_index' ) ].indent;
+				for ( i = startItem.getCustomData( 'listarray_index' ); i <= lastItem.getCustomData( 'listarray_index' ); i++ )
+				{
+					listArray[ i ].indent += indentOffset;
+					// Make sure the newly created sublist get a brand-new element of the same type. (#5372)
+					var listRoot = listArray[ i ].parent;
+					listArray[ i ].parent = new CKEDITOR.dom.element( listRoot.getName(), listRoot.getDocument() );
+				}
+
+				for ( i = lastItem.getCustomData( 'listarray_index' ) + 1 ;
+						i < listArray.length && listArray[i].indent > baseIndent ; i++ )
+					listArray[i].indent += indentOffset;
+
+				// Convert the array back to a DOM forest (yes we might have a few subtrees now).
+				// And replace the old list with the new forest.
+				var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode, 0 );
+
+				// Avoid nested <li> after outdent even they're visually same,
+				// recording them for later refactoring.(#3982)
+				if ( self.name == 'outdent' )
+				{
+					var parentLiElement;
+					if ( ( parentLiElement = listNode.getParent() ) && parentLiElement.is( 'li' ) )
+					{
+						var children = newList.listNode.getChildren(),
+							pendingLis = [],
+							count = children.count(),
+							child;
+
+						for ( i = count - 1 ; i >= 0 ; i-- )
+						{
+							if ( ( child = children.getItem( i ) ) && child.is && child.is( 'li' )  )
+								pendingLis.push( child );
+						}
+					}
+				}
+
+				if ( newList )
+					newList.listNode.replace( listNode );
+
+				// Move the nested <li> to be appeared after the parent.
+				if ( pendingLis && pendingLis.length )
+				{
+					for (  i = 0; i < pendingLis.length ; i++ )
+					{
+						var li = pendingLis[ i ],
+							followingList = li;
+
+						// Nest preceding <ul>/<ol> inside current <li> if any.
+						while ( ( followingList = followingList.getNext() ) &&
+							   followingList.is &&
+							   followingList.getName() in listNodeNames )
+						{
+							// IE requires a filler NBSP for nested list inside empty list item,
+							// otherwise the list item will be inaccessiable. (#4476)
+							if ( CKEDITOR.env.ie && !li.getFirst( function( node ){ return isNotWhitespaces( node ) && isNotBookmark( node ); } ) )
+								li.append( range.document.createText( '\u00a0' ) );
+
+							li.append( followingList );
+						}
+
+						li.insertAfter( parentLiElement );
+					}
+				}
+			}
+
+			function indentBlock()
+			{
+				var iterator = range.createIterator(),
+					enterMode = editor.config.enterMode;
+				iterator.enforceRealBlocks = true;
+				iterator.enlargeBr = enterMode != CKEDITOR.ENTER_BR;
+				var block;
+				while ( ( block = iterator.getNextParagraph() ) )
+					indentElement( block );
+			}
+
+			function indentElement( element )
+			{
+				if ( element.getCustomData( 'indent_processed' ) )
+					return;
+
+				if ( self.useIndentClasses )
+				{
+					// Transform current class name to indent step index.
+					var indentClass = element.$.className.match( self.classNameRegex ),
+							indentStep = 0;
+					if ( indentClass )
+					{
+						indentClass = indentClass[1];
+						indentStep = self.indentClassMap[ indentClass ];
+					}
+
+					// Operate on indent step index, transform indent step index back to class
+					// name.
+					if ( self.name == 'outdent' )
+						indentStep--;
+					else
+						indentStep++;
+
+					if ( indentStep < 0 )
+						return false;
+
+					indentStep = Math.min( indentStep, editor.config.indentClasses.length );
+					indentStep = Math.max( indentStep, 0 );
+					var className = CKEDITOR.tools.ltrim( element.$.className.replace( self.classNameRegex, '' ) );
+					if ( indentStep < 1 )
+						element.$.className = className;
+					else
+						element.addClass( editor.config.indentClasses[ indentStep - 1 ] );
+				}
+				else
+				{
+					var currentOffset = parseInt( element.getStyle( self.indentCssProperty ), 10 );
+					if ( isNaN( currentOffset ) )
+						currentOffset = 0;
+					currentOffset += ( self.name == 'indent' ? 1 : -1 ) * editor.config.indentOffset;
+
+					if ( currentOffset < 0 )
+						return false;
+
+					currentOffset = Math.max( currentOffset, 0 );
+					currentOffset = Math.ceil( currentOffset / editor.config.indentOffset ) * editor.config.indentOffset;
+					element.setStyle( self.indentCssProperty, currentOffset ? currentOffset + editor.config.indentUnit : '' );
+					if ( element.getAttribute( 'style' ) === '' )
+						element.removeAttribute( 'style' );
+				}
+
+				CKEDITOR.dom.element.setMarker( database, element, 'indent_processed', true );
+				return true;
+			}
+
 			var selection = editor.getSelection(),
-				range = selection && selection.getRanges()[0];
-
-			var startContainer = range.startContainer,
-				endContainer = range.endContainer,
-				rangeRoot = range.getCommonAncestor(),
-				nearestListBlock = rangeRoot;
-
-			while ( nearestListBlock && !( nearestListBlock.type == CKEDITOR.NODE_ELEMENT &&
-				listNodeNames[ nearestListBlock.getName() ] ) )
-				nearestListBlock = nearestListBlock.getParent();
-
-			// Avoid selection anchors under list root.
-			// <ul>[<li>...</li>]</ul> =>	<ul><li>[...]</li></ul>
-			if ( nearestListBlock && startContainer.type == CKEDITOR.NODE_ELEMENT
-				&& startContainer.getName() in listNodeNames )
-			{
-				var walker = new CKEDITOR.dom.walker( range );
-				walker.evaluator = isListItem;
-				range.startContainer = walker.next();
-			}
-
-			if ( nearestListBlock && endContainer.type == CKEDITOR.NODE_ELEMENT
-				&& endContainer.getName() in listNodeNames )
-			{
-				walker = new CKEDITOR.dom.walker( range );
-				walker.evaluator = isListItem;
-				range.endContainer = walker.previous();
-			}
-
-			var bookmarks = selection.createBookmarks( true );
-
-			if ( nearestListBlock  )
-			{
-				var firstListItem = nearestListBlock.getFirst( function( node )
-					{
-						return node.type == CKEDITOR.NODE_ELEMENT && node.is( 'li' );
-					}),
-					rangeStart = range.startContainer,
-					indentWholeList = firstListItem.equals( rangeStart ) || firstListItem.contains( rangeStart );
-
-				// Indent the entire list if  cursor is inside the first list item. (#3893)
-				if ( !( indentWholeList && indentElement.call( this, editor, nearestListBlock ) ) )
-				indentList.call( this, editor, range, nearestListBlock );
-			}
-			else
-				indentBlock.call( this, editor, range );
-
-			editor.focus();
+				bookmarks = selection.createBookmarks( true ),
+				ranges = selection && selection.getRanges( true ),
+				range;
+
+			var iterator = ranges.createIterator();
+			while ( range = iterator.getNextRange() )
+			{
+				var startContainer = range.startContainer,
+					endContainer = range.endContainer,
+					rangeRoot = range.getCommonAncestor(),
+					nearestListBlock = rangeRoot;
+
+				while ( nearestListBlock && !( nearestListBlock.type == CKEDITOR.NODE_ELEMENT &&
+					listNodeNames[ nearestListBlock.getName() ] ) )
+					nearestListBlock = nearestListBlock.getParent();
+
+				// Avoid selection anchors under list root.
+				// <ul>[<li>...</li>]</ul> =>	<ul><li>[...]</li></ul>
+				if ( nearestListBlock && startContainer.type == CKEDITOR.NODE_ELEMENT
+					&& startContainer.getName() in listNodeNames )
+				{
+					var walker = new CKEDITOR.dom.walker( range );
+					walker.evaluator = isListItem;
+					range.startContainer = walker.next();
+				}
+
+				if ( nearestListBlock && endContainer.type == CKEDITOR.NODE_ELEMENT
+					&& endContainer.getName() in listNodeNames )
+				{
+					walker = new CKEDITOR.dom.walker( range );
+					walker.evaluator = isListItem;
+					range.endContainer = walker.previous();
+				}
+
+				if ( nearestListBlock  )
+				{
+					var firstListItem = nearestListBlock.getFirst( function( node )
+						{
+							return node.type == CKEDITOR.NODE_ELEMENT && node.is( 'li' );
+						}),
+						rangeStart = range.startContainer,
+						indentWholeList = firstListItem.equals( rangeStart ) || firstListItem.contains( rangeStart );
+
+					// Indent the entire list if  cursor is inside the first list item. (#3893)
+					if ( !( indentWholeList && indentElement( nearestListBlock ) ) )
+					indentList( nearestListBlock );
+				}
+				else
+					indentBlock();
+			}
+
+			// Clean up the markers.
+			CKEDITOR.dom.element.clearAllMarkers( database );
+
 			editor.forceNextSelectionCheck();
 			selection.selectBookmarks( bookmarks );
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/justify/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/justify/plugin.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/justify/plugin.js	(revision 5709)
@@ -1,3 +1,3 @@
-﻿/*
+/*
 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
 For licensing, see LICENSE.html or http://ckeditor.com/license
@@ -74,5 +74,5 @@
 
 			var bookmarks = selection.createBookmarks(),
-				ranges = selection.getRanges();
+				ranges = selection.getRanges( true );
 
 
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/link/dialogs/link.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/link/dialogs/link.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/link/dialogs/link.js	(revision 5709)
@@ -1295,5 +1295,5 @@
 				// Create element if current selection is collapsed.
 				var selection = editor.getSelection(),
-					ranges = selection.getRanges();
+					ranges = selection.getRanges( true );
 				if ( ranges.length == 1 && ranges[0].collapsed )
 				{
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/link/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/link/plugin.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/link/plugin.js	(revision 5709)
@@ -1,3 +1,3 @@
-﻿/*
+/*
 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
 For licensing, see LICENSE.html or http://ckeditor.com/license
@@ -111,5 +111,5 @@
 			editor.contextMenu.addListener( function( element, selection )
 				{
-					if ( !element )
+					if ( !element || element.isReadOnly() )
 						return null;
 
@@ -178,10 +178,12 @@
 	{
 		var range;
-		try { range  = editor.getSelection().getRanges()[ 0 ]; }
+		try
+		{
+			range  = editor.getSelection().getRanges( true )[ 0 ];
+			range.shrink( CKEDITOR.SHRINK_TEXT );
+			var root = range.getCommonAncestor();
+			return root.getAscendant( 'a', true );
+		}
 		catch( e ) { return null; }
-
-		range.shrink( CKEDITOR.SHRINK_TEXT );
-		var root = range.getCommonAncestor();
-		return root.getAscendant( 'a', true );
 	}
 };
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/list/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/list/plugin.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/list/plugin.js	(revision 5709)
@@ -1,3 +1,3 @@
-﻿/*
+/*
 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
 For licensing, see LICENSE.html or http://ckeditor.com/license
@@ -401,5 +401,5 @@
 			var doc = editor.document,
 				selection = editor.getSelection(),
-				ranges = selection && selection.getRanges();
+				ranges = selection && selection.getRanges( true );
 
 			// There should be at least one selected range.
@@ -449,10 +449,10 @@
 			// or multiple lists have to be cancelled.
 			var listGroups = [],
-				database = {};
-
-			while ( ranges.length > 0 )
-			{
-				range = ranges.shift();
-
+				database = {},
+				rangeIterator = ranges.createIterator(),
+				index = 0;
+
+			while ( ( range = rangeIterator.getNextRange() ) && ++index )
+			{
 				var boundaryNodes = range.getBoundaryNodes(),
 					startNode = boundaryNodes.startNode,
@@ -472,4 +472,10 @@
 				while ( ( block = iterator.getNextParagraph() ) )
 				{
+					// Avoid duplicate blocks get processed across ranges.
+					if( block.getCustomData( 'list_block' ) )
+						continue;
+					else
+						CKEDITOR.dom.element.setMarker( database, block, 'list_block', 1 );
+
 					var path = new CKEDITOR.dom.elementPath( block ),
 						pathElements = path.elements,
@@ -491,5 +497,5 @@
 							// should belong to a different group of paragraphs before
 							// the list. (Bug #1309)
-							blockLimit.removeCustomData( 'list_group_object' );
+							blockLimit.removeCustomData( 'list_group_object_' + index );
 
 							var groupObj = element.getCustomData( 'list_group_object' );
@@ -510,12 +516,12 @@
 						continue;
 
-					// No list ancestor? Group by block limit.
+					// No list ancestor? Group by block limit, but don't mix contents from different ranges.
 					var root = blockLimit;
-					if ( root.getCustomData( 'list_group_object' ) )
-						root.getCustomData( 'list_group_object' ).contents.push( block );
+					if ( root.getCustomData( 'list_group_object_' + index ) )
+						root.getCustomData( 'list_group_object_' + index ).contents.push( block );
 					else
 					{
 						groupObj = { root : root, contents : [ block ] };
-						CKEDITOR.dom.element.setMarker( database, root, 'list_group_object', groupObj );
+						CKEDITOR.dom.element.setMarker( database, root, 'list_group_object_' + index, groupObj );
 						listGroups.push( groupObj );
 					}
@@ -551,5 +557,5 @@
 						'getPrevious' : 'getNext' ]( CKEDITOR.dom.walker.whitespaces( true ) );
 					if ( sibling && sibling.getName &&
-					     sibling.getName() == listCommand.type )
+						 sibling.getName() == listCommand.type )
 					{
 						sibling.remove();
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/liststyle/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/liststyle/plugin.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/liststyle/plugin.js	(revision 5709)
@@ -43,5 +43,5 @@
 				editor.contextMenu.addListener( function( element, selection )
 					{
-						if ( !element )
+						if ( !element || element.isReadOnly() )
 							return null;
 
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/pagebreak/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/pagebreak/plugin.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/pagebreak/plugin.js	(revision 5709)
@@ -83,13 +83,13 @@
 		breakObject = editor.createFakeElement( breakObject, 'cke_pagebreak', 'div' );
 
-		var ranges = editor.getSelection().getRanges();
+		var ranges = editor.getSelection().getRanges( true );
 
 		editor.fire( 'saveSnapshot' );
 
-		for ( var range, i = 0 ; i < ranges.length ; i++ )
+		for ( var range, i = ranges.length - 1 ; i >= 0; i-- )
 		{
 			range = ranges[ i ];
 
-			if ( i > 0 )
+			if ( i < ranges.length -1 )
 				breakObject = breakObject.clone( true );
 
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/removeformat/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/removeformat/plugin.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/removeformat/plugin.js	(revision 5709)
@@ -1,3 +1,3 @@
-﻿/*
+/*
 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
 For licensing, see LICENSE.html or http://ckeditor.com/license
@@ -36,7 +36,9 @@
 
 				var filter = CKEDITOR.plugins.removeformat.filter;
-				var ranges = editor.getSelection().getRanges();
+				var ranges = editor.getSelection().getRanges( true ),
+					iterator = ranges.createIterator(),
+					range;
 
-				for ( var i = 0, range ; range = ranges[ i ] ; i++ )
+				while ( range = iterator.getNextRange() )
 				{
 					if ( range.collapsed )
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/scayt/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/scayt/plugin.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/scayt/plugin.js	(revision 5709)
@@ -625,7 +625,8 @@
 			if ( editor.contextMenu && editor.addMenuItems )
 			{
-				editor.contextMenu.addListener( function(  )
-					{
-						if ( !plugin.isScaytEnabled( editor ) )
+				editor.contextMenu.addListener( function( element, selection )
+					{
+						if ( !plugin.isScaytEnabled( editor )
+								|| selection.getCommonAncestor().isReadOnly() )
 							return null;
 
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/selection/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/selection/plugin.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/selection/plugin.js	(revision 5709)
@@ -1,3 +1,3 @@
-/*
+﻿/*
 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
 For licensing, see LICENSE.html or http://ckeditor.com/license
@@ -485,6 +485,7 @@
 		 * alert(ranges.length);
 		 */
-		getRanges :
-			CKEDITOR.env.ie ?
+		getRanges : (function ()
+		{
+			var func = CKEDITOR.env.ie ?
 				( function()
 				{
@@ -574,8 +575,4 @@
 					return function()
 					{
-						var cache = this._.cache;
-						if ( cache.ranges )
-							return cache.ranges;
-
 						// IE doesn't have range support (in the W3C way), so we
 						// need to do some magic to transform selections into
@@ -600,9 +597,9 @@
 							range.setEnd( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset );
 
-							return ( cache.ranges = [ range ] );
+							return [ range ];
 						}
 						else if ( type == CKEDITOR.SELECTION_ELEMENT )
 						{
-							var retval = this._.cache.ranges = [];
+							var retval = [];
 
 							for ( var i = 0 ; i < nativeRange.length ; i++ )
@@ -625,5 +622,5 @@
 						}
 
-						return ( cache.ranges = [] );
+						return [];
 					};
 				})()
@@ -631,7 +628,4 @@
 				function()
 				{
-					var cache = this._.cache;
-					if ( cache.ranges )
-						return cache.ranges;
 
 					// On browsers implementing the W3C range, we simply
@@ -654,7 +648,99 @@
 						ranges.push( range );
 					}
-
-					return ( cache.ranges = ranges );
-				},
+					return ranges;
+				};
+
+			return function( onlyEditables )
+			{
+				var cache = this._.cache;
+				if ( cache.ranges && !onlyEditables )
+					return cache.ranges;
+				else if ( !cache.ranges )
+					cache.ranges = new CKEDITOR.dom.rangeList( func.call( this ) );
+
+				// Split range into multiple by read-only nodes.
+				if ( onlyEditables )
+				{
+					var ranges = cache.ranges;
+					for ( var i = 0; i < ranges.length; i++ )
+					{
+						var range = ranges[ i ];
+
+						// Drop range spans inside one ready-only node.
+						var parent = range.getCommonAncestor();
+						if ( parent.isReadOnly())
+							ranges.splice( i, 1 );
+
+						if ( range.collapsed )
+							continue;
+
+						var startContainer = range.startContainer,
+							endContainer = range.endContainer,
+							startOffset = range.startOffset,
+							endOffset = range.endOffset,
+							walkerRange = range.clone();
+
+						// Range may start inside a non-editable element, restart range
+						// by the end of it.
+						var readOnly;
+						if ( readOnly = startContainer.isReadOnly() )
+							range.setStartAfter( readOnly );
+
+						// Enlarge range start/end with text node to avoid walker
+						// being DOM destructive, it doesn't interfere our checking
+						// of elements below as well.
+						if ( startContainer && startContainer.type == CKEDITOR.NODE_TEXT )
+						{
+							if ( startOffset >= startContainer.getLength() )
+								walkerRange.setStartAfter( startContainer );
+							else
+								walkerRange.setStartBefore( startContainer );
+						}
+
+						if ( endContainer && endContainer.type == CKEDITOR.NODE_TEXT )
+						{
+							if ( !endOffset )
+								walkerRange.setEndBefore( endContainer );
+							else
+								walkerRange.setEndAfter( endContainer );
+						}
+
+						// Looking for non-editable element inside the range.
+						var walker = new CKEDITOR.dom.walker( walkerRange );
+						walker.evaluator = function( node )
+						{
+							if ( node.type == CKEDITOR.NODE_ELEMENT
+								&& node.getAttribute( 'contenteditable' ) == 'false' )
+							{
+								var newRange = range.clone();
+								range.setEndBefore( node );
+
+								// Drop collapsed range around read-only elements,
+								// it make sure the range list empty when selecting
+								// only non-editable elements.
+								if ( range.collapsed )
+									ranges.splice( i--, 1 );
+								
+								// Avoid creating invalid range.
+								if ( !( node.getPosition( walkerRange.endContainer ) & CKEDITOR.POSITION_CONTAINS ) )
+								{
+									newRange.setStartAfter( node );
+									if ( !newRange.collapsed )
+										ranges.splice( i + 1, 0, newRange );
+								}
+
+								return true;
+							}
+
+							return false;
+						};
+
+						walker.next();
+					}
+				}
+
+				return cache.ranges;
+			}
+		})(),
 
 		/**
@@ -860,5 +946,5 @@
 				this._.cache.selectedElement = element;
 				this._.cache.startElement = element;
-				this._.cache.ranges = [ range ];
+				this._.cache.ranges = new CKEDITOR.dom.rangeList( range );
 				this._.cache.type = CKEDITOR.SELECTION_ELEMENT;
 
@@ -917,5 +1003,5 @@
 				this._.cache.selectedElement = null;
 				this._.cache.startElement = ranges[ 0 ].getTouchedStartNode();
-				this._.cache.ranges = ranges;
+				this._.cache.ranges = new CKEDITOR.dom.rangeList( ranges );
 				this._.cache.type = CKEDITOR.SELECTION_TEXT;
 
@@ -925,6 +1011,12 @@
 			if ( CKEDITOR.env.ie )
 			{
-				// IE doesn't accept multiple ranges selection, so we just
-				// select the first one.
+				if ( ranges.length > 1 )
+				{
+					// IE doesn't accept multiple ranges selection, so we join all into one.
+					var last = ranges[ ranges.length -1 ] ;
+					ranges[ 0 ].setEnd( last.endContainer, last.endOffset );
+					ranges.length = 1;
+				}
+
 				if ( ranges[ 0 ] )
 					ranges[ 0 ].select();
@@ -935,8 +1027,33 @@
 			{
 				var sel = this.getNative();
-				sel.removeAllRanges();
+
+				if ( ranges.length )
+					sel.removeAllRanges();
 
 				for ( var i = 0 ; i < ranges.length ; i++ )
 				{
+					// Joining sequential ranges introduced by
+					// readonly elements protection.
+					if ( i < ranges.length -1 )
+					{
+						var left = ranges[ i ], right = ranges[ i +1 ],
+								between = left.clone();
+						between.setStart( left.endContainer, left.endOffset );
+						between.setEnd( right.startContainer, right.startOffset );
+
+						// Don't confused by Firefox adjancent multi-ranges
+						// introduced by table cells selection.
+						if ( !between.collapsed )
+						{
+							between.shrink( CKEDITOR.NODE_ELEMENT, true );
+							if ( between.getCommonAncestor().isReadOnly())
+							{
+								right.setStart( left.startContainer, left.startOffset );
+								ranges.splice( i--, 1 );
+								continue;
+							}
+						}
+					}
+
 					var range = ranges[ i ];
 					var nativeRange = this.document.$.createRange();
@@ -973,32 +1090,5 @@
 		createBookmarks : function( serializable )
 		{
-			var retval = [],
-				ranges = this.getRanges(),
-				length = ranges.length,
-				bookmark;
-			for ( var i = 0; i < length ; i++ )
-			{
-			    retval.push( bookmark = ranges[ i ].createBookmark( serializable, true ) );
-
-				serializable = bookmark.serializable;
-
-				var bookmarkStart = serializable ? this.document.getById( bookmark.startNode ) : bookmark.startNode,
-					bookmarkEnd = serializable ? this.document.getById( bookmark.endNode ) : bookmark.endNode;
-
-			    // Updating the offset values for rest of ranges which have been mangled(#3256).
-			    for ( var j = i + 1 ; j < length ; j++ )
-			    {
-			        var dirtyRange = ranges[ j ],
-			               rangeStart = dirtyRange.startContainer,
-			               rangeEnd = dirtyRange.endContainer;
-
-			       rangeStart.equals( bookmarkStart.getParent() ) && dirtyRange.startOffset++;
-			       rangeStart.equals( bookmarkEnd.getParent() ) && dirtyRange.startOffset++;
-			       rangeEnd.equals( bookmarkStart.getParent() ) && dirtyRange.endOffset++;
-			       rangeEnd.equals( bookmarkEnd.getParent() ) && dirtyRange.endOffset++;
-			    }
-			}
-
-			return retval;
+			return this.getRanges().createBookmarks( serializable );
 		},
 
@@ -1011,11 +1101,5 @@
 		createBookmarks2 : function( normalized )
 		{
-			var bookmarks = [],
-				ranges = this.getRanges();
-
-			for ( var i = 0 ; i < ranges.length ; i++ )
-				bookmarks.push( ranges[i].createBookmark2( normalized ) );
-
-			return bookmarks;
+			return this.getRanges().createBookmarks2( normalized );
 		},
 
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/specialchar/dialogs/specialchar.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/specialchar/dialogs/specialchar.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/specialchar/dialogs/specialchar.js	(revision 5709)
@@ -1,3 +1,3 @@
-﻿/*
+/*
 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
 For licensing, see LICENSE.html or http://ckeditor.com/license
@@ -16,15 +16,15 @@
 	{
 		var selection = editor.getSelection(),
-			ranges	  = selection.getRanges(),
+			ranges = selection.getRanges( true ),
 			range, textNode;
 
 		editor.fire( 'saveSnapshot' );
 
-		for ( var i = 0, len = ranges.length ; i < len ; i++ )
+		for ( var i = ranges.length - 1; i >= 0 ; i-- )
 		{
 			range = ranges[ i ];
 			range.deleteContents();
 
-			textNode =  CKEDITOR.dom.element.createFromHtml( specialChar );
+			textNode = CKEDITOR.dom.element.createFromHtml( specialChar );
 			range.insertNode( textNode );
 		}
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/styles/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/styles/plugin.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/styles/plugin.js	(revision 5709)
@@ -377,7 +377,4 @@
 		var dtd = CKEDITOR.dtd[ elementName ] || ( isUnknownElement = true, CKEDITOR.dtd.span );
 
-		// Bookmark the range so we can re-select it after processing.
-		var bookmark = range.createBookmark();
-
 		// Expand the range.
 		range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
@@ -547,5 +544,5 @@
 		firstNode.remove();
 		lastNode.remove();
-		range.moveToBookmark( bookmark );
+
 		// Minimize the result range to exclude empty text nodes. (#5374)
 		range.shrink( CKEDITOR.SHRINK_TEXT );
@@ -1275,15 +1272,16 @@
 	function applyStyle( document, remove )
 	{
-		// Get all ranges from the selection.
-		var selection = document.getSelection();
-		var ranges = selection.getRanges();
-		var func = remove ? this.removeFromRange : this.applyToRange;
-
-		// Apply the style to the ranges.
-		for ( var i = 0 ; i < ranges.length ; i++ )
-			func.call( this, ranges[ i ] );
-
-		// Select the ranges again.
-		selection.selectRanges( ranges );
+		var selection = document.getSelection(),
+			// Bookmark the range so we can re-select it after processing.
+			bookmarks = selection.createBookmarks(),
+			ranges = selection.getRanges( true ),
+			func = remove ? this.removeFromRange : this.applyToRange,
+			range;
+
+		var iterator = ranges.createIterator();
+		while ( range = iterator.getNextRange() )
+			func.call( this, range );
+
+		selection.selectBookmarks( bookmarks );
 	}
 })();
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/table/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/table/plugin.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/table/plugin.js	(revision 5709)
@@ -1,3 +1,3 @@
-﻿/*
+/*
 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
 For licensing, see LICENSE.html or http://ckeditor.com/license
@@ -59,5 +59,5 @@
 			editor.contextMenu.addListener( function( element, selection )
 				{
-					if ( !element )
+					if ( !element || element.isReadOnly())
 						return null;
 
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/tabletools/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/tabletools/plugin.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/tabletools/plugin.js	(revision 5709)
@@ -1,3 +1,3 @@
-﻿/*
+/*
 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
 For licensing, see LICENSE.html or http://ckeditor.com/license
@@ -1044,5 +1044,5 @@
 				editor.contextMenu.addListener( function( element, selection )
 					{
-						if ( !element )
+						if ( !element || element.isReadOnly() )
 							return null;
 
Index: /CKEditor/branches/versions/3.4.x/_source/plugins/wysiwygarea/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/_source/plugins/wysiwygarea/plugin.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/_source/plugins/wysiwygarea/plugin.js	(revision 5709)
@@ -16,4 +16,9 @@
 	// Matching an empty paragraph at the end of document.
 	var emptyParagraphRegexp = /\s*<(p|div|address|h\d|center|li)[^>]*>\s*(?:<br[^>]*>|&nbsp;|\u00A0|&#160;)?\s*(:?<\/\1>)?\s*(?=$|<\/body>)/gi;
+
+	function checkReadOnly( selection )
+	{
+		return selection.getCommonAncestor().isReadOnly();
+	}
 
 	function onInsertHtml( evt )
@@ -22,8 +27,12 @@
 		{
 			this.focus();
+
+			var selection = this.getSelection();
+			if ( checkReadOnly( selection ) )
+				return;
+
+			var data = evt.data
 			this.fire( 'saveSnapshot' );
 
-			var selection = this.getSelection(),
-				data = evt.data;
 
 			if ( this.dataProcessor )
@@ -60,12 +69,15 @@
 		{
 			this.focus();
+
+			var selection = this.getSelection();
+			if ( checkReadOnly( selection ) )
+				return;
+
 			this.fire( 'saveSnapshot' );
 
-			var element = evt.data,
+			var ranges = selection.getRanges(),
+				element = evt.data,
 				elementName = element.getName(),
 				isBlock = CKEDITOR.dtd.$block[ elementName ];
-
-			var selection = this.getSelection(),
-				ranges = selection.getRanges();
 
 			var selIsLocked = selection.isLocked;
@@ -222,12 +234,18 @@
 				if ( previousElement && previousElement.getName
 					 && !( previousElement.getName() in nonExitableElementNames )
-					 && isBlankParagraph( previousElement )
-					 && range.moveToElementEditStart( previousElement )
 					 || nextElement && nextElement.getName
-						&& !( nextElement.getName() in nonExitableElementNames )
-						&& isBlankParagraph( nextElement )
-						&& range.moveToElementEditStart( nextElement ) )
+						&& !( nextElement.getName() in nonExitableElementNames ) )
 				{
-					fixedBlock.remove();
+					if ( isBlankParagraph( previousElement ) && range.moveToElementEditStart( previousElement )
+							|| isBlankParagraph( nextElement ) && range.moveToElementEditStart( nextElement ) )
+						fixedBlock.remove();
+
+					// Firefox prefer to anchor cursor after non-editable elements
+					// when navigate to them, leaving native behavior untouched. (#5834)
+					if ( CKEDITOR.env.gecko && ( previousElement.isReadOnly() || nextElement.isReadOnly() ) )
+					{
+						range.moveToPosition( fixedBlock, CKEDITOR.POSITION_BEFORE_START );
+						fixedBlock.remove();
+					}
 				}
 			}
Index: /CKEditor/branches/versions/3.4.x/ckeditor.pack
===================================================================
--- /CKEditor/branches/versions/3.4.x/ckeditor.pack	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/ckeditor.pack	(revision 5709)
@@ -1,3 +1,3 @@
-/*
+﻿/*
  * CKPackager - Sample Package file
  */
@@ -130,4 +130,5 @@
 					'_source/core/dom/walker.js',
 					'_source/core/dom/range.js',
+					'_source/core/dom/rangelist.js',
 					'_source/core/_bootstrap.js',
 					'_source/skins/kama/skin.js',
Index: /CKEditor/branches/versions/3.4.x/config.js
===================================================================
--- /CKEditor/branches/versions/3.4.x/config.js	(revision 5708)
+++ /CKEditor/branches/versions/3.4.x/config.js	(revision 5709)
@@ -1,3 +1,3 @@
-﻿/*
+/*
 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
 For licensing, see LICENSE.html or http://ckeditor.com/license
