Index: /CKEditor/branches/versions/3.5.x/CHANGES.html
===================================================================
--- /CKEditor/branches/versions/3.5.x/CHANGES.html	(revision 6142)
+++ /CKEditor/branches/versions/3.5.x/CHANGES.html	(revision 6143)
@@ -47,4 +47,5 @@
 		<li><a href="http://dev.ckeditor.com/ticket/6103">#6103</a> : It's now possible to control the styling of inline read-only elements with the disableReadonlyStyling setting. It's also possible to avoid inline-styling any element by setting its data-cke-nostyle attribute to "1".</li>
 		<li><a href="http://dev.ckeditor.com/ticket/5404">#5404</a> : "fillEmptyBlocks" configuration option of v2 is now available.</li>
+		<li><a href="http://dev.ckeditor.com/ticket/5367">#5367</a> : New "CKEDITOR.editor::insertText" method (check api.html sample page for usages) is now provided to insert plain text into editor.</li>
 	</ul>
 	<p>
Index: /CKEditor/branches/versions/3.5.x/_source/core/dom/element.js
===================================================================
--- /CKEditor/branches/versions/3.5.x/_source/core/dom/element.js	(revision 6142)
+++ /CKEditor/branches/versions/3.5.x/_source/core/dom/element.js	(revision 6143)
@@ -1,3 +1,3 @@
-/*
+﻿/*
 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
 For licensing, see LICENSE.html or http://ckeditor.com/license
@@ -726,4 +726,7 @@
 				var attribute = thisAttribs[ i ];
 
+				if ( attribute.nodeName == '_moz_dirty' )
+					continue;
+
 				if ( ( !CKEDITOR.env.ie || ( attribute.specified && attribute.nodeName != 'data-cke-expando' ) ) && attribute.nodeValue != otherElement.getAttribute( attribute.nodeName ) )
 					return false;
Index: /CKEditor/branches/versions/3.5.x/_source/core/editor.js
===================================================================
--- /CKEditor/branches/versions/3.5.x/_source/core/editor.js	(revision 6142)
+++ /CKEditor/branches/versions/3.5.x/_source/core/editor.js	(revision 6143)
@@ -757,4 +757,20 @@
 
 		/**
+		 * Insert text content into the currently selected position in the
+		 * editor, in WYSIWYG mode, styles of the selected element will be applied to the inserted text,
+		 * spaces around the text will be leaving untouched.
+		 * <strong>Note:</strong> two subsequent line-breaks will introduce one paragraph, which element depends on {@link CKEDITOR.config.enterMode};
+		 * A single line-break will be instead translated into one &lt;br /&gt;.
+		 * @since 3.5
+		 * @param {String} text Text to be inserted into the editor.
+		 * @example
+		 * CKEDITOR.instances.editor1.<b>insertText( ' line1 \n\n line2' )</b>;
+		 */
+		insertText : function( text )
+		{
+			this.fire( 'insertText', text );
+		},
+
+		/**
 		 * Inserts an element into the currently selected position in the
 		 * editor.
Index: /CKEditor/branches/versions/3.5.x/_source/plugins/htmldataprocessor/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.5.x/_source/plugins/htmldataprocessor/plugin.js	(revision 6142)
+++ /CKEditor/branches/versions/3.5.x/_source/plugins/htmldataprocessor/plugin.js	(revision 6143)
@@ -1,3 +1,3 @@
-/*
+﻿/*
 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
 For licensing, see LICENSE.html or http://ckeditor.com/license
@@ -185,4 +185,11 @@
 						return false;
 					}
+				},
+
+				// Remove dummy span in webkit.
+				span: function( element )
+				{
+					if ( element.attributes[ 'class' ] == 'Apple-style-span' )
+						delete element.name;
 				},
 
Index: /CKEditor/branches/versions/3.5.x/_source/plugins/pastetext/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.5.x/_source/plugins/pastetext/plugin.js	(revision 6142)
+++ /CKEditor/branches/versions/3.5.x/_source/plugins/pastetext/plugin.js	(revision 6143)
@@ -38,18 +38,4 @@
 	};
 
-	function doInsertText( doc, text )
-	{
-		// Native text insertion.
-		if ( CKEDITOR.env.ie )
-		{
-			var selection = doc.selection;
-			if ( selection.type == 'Control' )
-				selection.clear();
-			selection.createRange().pasteHTML( text );
-		}
-		else
-			doc.execCommand( 'inserthtml', false, text );
-	}
-
 	// Register the plugin.
 	CKEDITOR.plugins.add( 'pastetext',
@@ -85,50 +71,4 @@
 	});
 
-	function doEnter( editor, mode, times, forceMode )
-	{
-		while ( times-- )
-		{
-			CKEDITOR.plugins.enterkey[ mode == CKEDITOR.ENTER_BR ? 'enterBr' : 'enterBlock' ]
-					( editor, mode, null, forceMode );
-		}
-	}
-
-	CKEDITOR.editor.prototype.insertText = function( text )
-	{
-		this.focus();
-		this.fire( 'saveSnapshot' );
-
-		var mode = this.getSelection().getStartElement().hasAscendant( 'pre', true ) ? CKEDITOR.ENTER_BR : this.config.enterMode,
-			isEnterBrMode = mode == CKEDITOR.ENTER_BR,
-			doc = this.document.$,
-			self = this,
-			line;
-
-		text = CKEDITOR.tools.htmlEncode( text.replace( /\r\n|\r/g, '\n' ) );
-
-		var startIndex = 0;
-		text.replace( /\n+/g, function( match, lastIndex )
-		 {
-			line = text.substring( startIndex, lastIndex );
-			startIndex = lastIndex + match.length;
-			line.length && doInsertText( doc, line );
-
-			var lineBreakNums = match.length,
-				// Duo consequence line-break as a enter block.
-				enterBlockTimes = isEnterBrMode ? 0 : Math.floor( lineBreakNums / 2 ),
-				// Per link-break as a enter br.
-				enterBrTimes = isEnterBrMode ? lineBreakNums : lineBreakNums % 2;
-
-			// Line-breaks are converted to editor enter key strokes.
-			doEnter( self, mode, enterBlockTimes );
-			doEnter( self, CKEDITOR.ENTER_BR, enterBrTimes, isEnterBrMode ? false : true );
-		 });
-
-		// Insert the last text line of text.
-		line = text.substring( startIndex, text.length );
-		line.length && doInsertText( doc, line );
-
-		this.fire( 'saveSnapshot' );
-	};
 })();
 
Index: /CKEditor/branches/versions/3.5.x/_source/plugins/specialchar/dialogs/specialchar.js
===================================================================
--- /CKEditor/branches/versions/3.5.x/_source/plugins/specialchar/dialogs/specialchar.js	(revision 6142)
+++ /CKEditor/branches/versions/3.5.x/_source/plugins/specialchar/dialogs/specialchar.js	(revision 6143)
@@ -13,30 +13,4 @@
 		lang = editor.lang.specialChar;
 
-	var insertSpecialChar = function ( specialChar )
-	{
-		var selection = editor.getSelection(),
-			ranges = selection.getRanges( true ),
-			range, textNode;
-
-		editor.fire( 'saveSnapshot' );
-
-		for ( var i = ranges.length - 1; i >= 0 ; i-- )
-		{
-			range = ranges[ i ];
-			range.deleteContents();
-
-			textNode = CKEDITOR.dom.element.createFromHtml( specialChar );
-			range.insertNode( textNode );
-		}
-
-		if ( range )
-		{
-			range.moveToPosition( textNode, CKEDITOR.POSITION_AFTER_END );
-			range.select();
-		}
-
-		editor.fire( 'saveSnapshot' );
-	};
-
 	var onChoice = function( evt )
 	{
@@ -52,9 +26,5 @@
 			dialog.hide();
 
-			// Firefox has bug on insert chars into a element use its own API. (#5170)
-			if ( CKEDITOR.env.gecko )
-				insertSpecialChar( value );
-			else
-				editor.insertHtml( value );
+			editor.insertText( value );
 		}
 	};
Index: /CKEditor/branches/versions/3.5.x/_source/plugins/wysiwygarea/plugin.js
===================================================================
--- /CKEditor/branches/versions/3.5.x/_source/plugins/wysiwygarea/plugin.js	(revision 6142)
+++ /CKEditor/branches/versions/3.5.x/_source/plugins/wysiwygarea/plugin.js	(revision 6143)
@@ -1,3 +1,3 @@
-/*
+﻿/*
 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
 For licensing, see LICENSE.html or http://ckeditor.com/license
@@ -34,175 +34,238 @@
 	}
 
-	function onInsertHtml( evt )
-	{
-		if ( this.mode == 'wysiwyg' )
-		{
-			this.focus();
-
-			var selection = this.getSelection();
-			if ( checkReadOnly( selection ) )
-				return;
-
-			var data = evt.data;
-			this.fire( 'saveSnapshot' );
-
-			if ( this.dataProcessor )
-				data = this.dataProcessor.toHtml( data );
-
-			if ( CKEDITOR.env.ie )
-			{
-				var selIsLocked = selection.isLocked;
-
-				if ( selIsLocked )
-					selection.unlock();
-
-				var $sel = selection.getNative();
-
-				// Delete control selections to avoid IE bugs on pasteHTML.
-				if ( $sel.type == 'Control' )
-					$sel.clear();
+
+	function onInsert( insertFunc )
+	{
+		return function( evt )
+		{
+			if ( this.mode == 'wysiwyg' )
+			{
+				this.focus();
+
+				var selection = this.getSelection();
+				if ( checkReadOnly( selection ) )
+					return;
+
+				this.fire( 'saveSnapshot' );
+
+				insertFunc.call( this, evt.data );
+
+				// Save snaps after the whole execution completed.
+				// This's a workaround for make DOM modification's happened after
+				// 'insertElement' to be included either, e.g. Form-based dialogs' 'commitContents'
+				// call.
+				CKEDITOR.tools.setTimeout( function()
+				   {
+					   this.fire( 'saveSnapshot' );
+				   }, 0, this );
+			}
+		}
+	}
+	
+	function doInsertHtml( data )
+	{
+		if ( this.dataProcessor )
+			data = this.dataProcessor.toHtml( data );
+
+		var selection = this.getSelection();
+		if ( CKEDITOR.env.ie )
+		{
+			var selIsLocked = selection.isLocked;
+
+			if ( selIsLocked )
+				selection.unlock();
+
+			var $sel = selection.getNative();
+
+			// Delete control selections to avoid IE bugs on pasteHTML.
+			if ( $sel.type == 'Control' )
+				$sel.clear();
 				else if  ( selection.getType() == CKEDITOR.SELECTION_TEXT )
+			{
+				// Due to IE bugs on handling contenteditable=false blocks
+				// (#6005), we need to make some checks and eventually
+				// delete the selection first.
+
+				var range = selection.getRanges()[0],
+						endContainer = range && range.endContainer;
+
+				if ( endContainer &&
+						endContainer.type == CKEDITOR.NODE_ELEMENT &&
+						endContainer.getAttribute( 'contenteditable' ) == 'false' &&
+						range.checkBoundaryOfElement( endContainer, CKEDITOR.END ) )
 				{
-					// Due to IE bugs on handling contenteditable=false blocks
-					// (#6005), we need to make some checks and eventually
-					// delete the selection first.
-
-					var range = selection.getRanges()[0],
-						endContainer = range && range.endContainer;
-
-					if ( endContainer &&
- 						 endContainer.type == CKEDITOR.NODE_ELEMENT &&
- 						 endContainer.getAttribute( 'contenteditable' ) == 'false' &&
-						 range.checkBoundaryOfElement( endContainer, CKEDITOR.END ) )
-					{
-						range.setEndAfter( range.endContainer );
-						range.deleteContents();
-					}
+					range.setEndAfter( range.endContainer );
+					range.deleteContents();
 				}
-
-				try
+			}
+
+			try
+			{
+				$sel.createRange().pasteHTML( data );
+			}
+				catch (e) {}
+
+			if ( selIsLocked )
+				this.getSelection().lock();
+		}
+		else
+			this.document.$.execCommand( 'inserthtml', false, data );
+
+		// Webkit does not scroll to the cursor position after pasting (#5558)
+		if ( CKEDITOR.env.webkit )
+		{
+			selection = this.getSelection();
+			selection.scrollIntoView();
+		}
+	}
+
+	function doInsertText( text )
+	{
+		var selection = this.getSelection(),
+			mode = selection.getStartElement().hasAscendant( 'pre', true ) ?
+				   CKEDITOR.ENTER_BR : this.config.enterMode,
+			isEnterBrMode = mode == CKEDITOR.ENTER_BR;
+
+		var html = CKEDITOR.tools.htmlEncode( text.replace( /\r\n|\r/g, '\n' ) );
+
+		// Convert leading and trailing whitespaces into &nbsp;
+		html = html.replace( /^[ \t]+|[ \t]+$/g, function( match, offset, s )
+			{
+				if ( match.length == 1 )	// one space, preserve it
+					return '&nbsp;';
+				else if ( !offset )		// beginning of block
+					return CKEDITOR.tools.repeat( '&nbsp;', match.length - 1 ) + ' ';
+				else				// end of block
+					return ' ' + CKEDITOR.tools.repeat( '&nbsp;', match.length - 1 );
+			} );
+
+		// Convert subsequent whitespaces into &nbsp;
+		html = html.replace( /[ \t]{2,}/g, function ( match )
+		   {
+			   return CKEDITOR.tools.repeat( '&nbsp;', match.length - 1 ) + ' ';
+		   } );
+
+		var paragraphTag = mode == CKEDITOR.ENTER_P ? 'p' : 'div';
+
+		// Two line-breaks create one paragraph.
+		if ( !isEnterBrMode )
+		{
+			html = html.replace( /(\n{2})([\s\S]*?)(?:$|\1)/g,
+				function( match, group1, text )
 				{
-					$sel.createRange().pasteHTML( data );
-				}
-				catch (e) {}
-
-				if ( selIsLocked )
-					this.getSelection().lock();
+					return '<'+paragraphTag + '>' + text + '</' + paragraphTag + '>';
+				});
+		}
+
+		// One <br> per line-break.
+		html = html.replace( /\n/g, '<br>' );
+
+		// Compensate padding <br> for non-IE.
+		if ( !( isEnterBrMode || CKEDITOR.env.ie ) )
+		{
+			html = html.replace( new RegExp( '<br>(?=</' + paragraphTag + '>)' ), function( match )
+			{
+				return CKEDITOR.tools.repeat( match, 2 );
+			} );
+		}
+
+		// Inline styles have to be inherited in Firefox.
+		if ( CKEDITOR.env.gecko || CKEDITOR.env.webkit )
+		{
+			var path = new CKEDITOR.dom.elementPath( selection.getStartElement() ),
+				context = [];
+
+			for ( var i = 0; i < path.elements.length; i++ )
+			{
+				var tag = path.elements[ i ].getName();
+				if ( tag in CKEDITOR.dtd.$inline )
+					context.unshift( path.elements[ i ].getOuterHtml().match( /^<.*?>/) );
+				else if ( tag in CKEDITOR.dtd.$block )
+					break;
 			}
-			else
-				this.document.$.execCommand( 'inserthtml', false, data );
-
-			// Webkit does not scroll to the cursor position after pasting (#5558)
-			if ( CKEDITOR.env.webkit )
-			{
-				this.document.$.execCommand( 'inserthtml', false, '<span id="cke_paste_marker" data-cke-temp="1"></span>' );
-				var marker = this.document.getById( 'cke_paste_marker' );
-				marker.scrollIntoView();
-				marker.remove();
-				marker = null;
-			}
-
-			CKEDITOR.tools.setTimeout( function()
-				{
-					this.fire( 'saveSnapshot' );
-				}, 0, this );
+
+			// Reproduce the context  by preceding the pasted HTML with opening inline tags.
+			html = context.join( '' ) + html;
 		}
-	}
-
-	function onInsertElement( evt )
-	{
-		if ( this.mode == 'wysiwyg' )
-		{
-			this.focus();
-
-			var selection = this.getSelection();
-			if ( checkReadOnly( selection ) )
-				return;
-
-			this.fire( 'saveSnapshot' );
-
-			var ranges = selection.getRanges(),
-				element = evt.data,
+
+		doInsertHtml.call( this, html );
+	}
+
+	function doInsertElement( element )
+	{
+		var selection = this.getSelection(),
+				ranges = selection.getRanges(),
 				elementName = element.getName(),
 				isBlock = CKEDITOR.dtd.$block[ elementName ];
 
-			var selIsLocked = selection.isLocked;
-
-			if ( selIsLocked )
-				selection.unlock();
-
-			var range, clone, lastElement, bookmark;
-
-			for ( var i = ranges.length - 1 ; i >= 0 ; i-- )
-			{
-				range = ranges[ i ];
-
-				// Remove the original contents.
-				range.deleteContents();
-
-				clone = !i && element || element.clone( 1 );
-
-				// If we're inserting a block at dtd-violated position, split
-				// the parent blocks until we reach blockLimit.
-				var current, dtd;
-				if ( isBlock )
+		var selIsLocked = selection.isLocked;
+
+		if ( selIsLocked )
+			selection.unlock();
+
+		var range, clone, lastElement, bookmark;
+
+		for ( var i = ranges.length - 1 ; i >= 0 ; i-- )
+		{
+			range = ranges[ i ];
+
+			// Remove the original contents.
+			range.deleteContents();
+
+			clone = !i && element || element.clone( 1 );
+
+			// If we're inserting a block at dtd-violated position, split
+			// the parent blocks until we reach blockLimit.
+			var current, dtd;
+			if ( isBlock )
+			{
+				while ( ( current = range.getCommonAncestor( 0, 1 ) )
+						&& ( dtd = CKEDITOR.dtd[ current.getName() ] )
+						&& !( dtd && dtd [ elementName ] ) )
 				{
-					while ( ( current = range.getCommonAncestor( 0, 1 ) )
-							&& ( dtd = CKEDITOR.dtd[ current.getName() ] )
-							&& !( dtd && dtd [ elementName ] ) )
+					// Split up inline elements.
+					if ( current.getName() in CKEDITOR.dtd.span )
+						range.splitElement( current );
+					// If we're in an empty block which indicate a new paragraph,
+					// simply replace it with the inserting block.(#3664)
+					else if ( range.checkStartOfBlock()
+							&& range.checkEndOfBlock() )
 					{
-						// Split up inline elements.
-						if ( current.getName() in CKEDITOR.dtd.span )
-							range.splitElement( current );
-						// If we're in an empty block which indicate a new paragraph,
-						// simply replace it with the inserting block.(#3664)
-						else if ( range.checkStartOfBlock()
-							 && range.checkEndOfBlock() )
-						{
-							range.setStartBefore( current );
-							range.collapse( true );
-							current.remove();
-						}
-						else
-							range.splitBlock();
+						range.setStartBefore( current );
+						range.collapse( true );
+						current.remove();
 					}
+					else
+						range.splitBlock();
 				}
-
-				// Insert the new node.
-				range.insertNode( clone );
-
-				// Save the last element reference so we can make the
-				// selection later.
-				if ( !lastElement )
-					lastElement = clone;
 			}
 
-			range.moveToPosition( lastElement, CKEDITOR.POSITION_AFTER_END );
-
-			// If we're inserting a block element immediatelly followed by
-			// another block element, the selection must move there. (#3100,#5436)
-			if ( isBlock )
-			{
-				var next = lastElement.getNext( notWhitespaceEval ),
+			// Insert the new node.
+			range.insertNode( clone );
+
+			// Save the last element reference so we can make the
+			// selection later.
+			if ( !lastElement )
+				lastElement = clone;
+		}
+
+		range.moveToPosition( lastElement, CKEDITOR.POSITION_AFTER_END );
+
+		// If we're inserting a block element immediatelly followed by
+		// another block element, the selection must move there. (#3100,#5436)
+		if ( isBlock )
+		{
+			var next = lastElement.getNext( notWhitespaceEval ),
 					nextName = next && next.type == CKEDITOR.NODE_ELEMENT && next.getName();
 
-				// Check if it's a block element that accepts text.
-				if ( nextName && CKEDITOR.dtd.$block[ nextName ] && CKEDITOR.dtd[ nextName ]['#'] )
-					range.moveToElementEditStart( next );
-			}
-
-			selection.selectRanges( [ range ] );
-
-			if ( selIsLocked )
-				this.getSelection().lock();
-
-			// Save snaps after the whole execution completed.
-			// This's a workaround for make DOM modification's happened after
-			// 'insertElement' to be included either, e.g. Form-based dialogs' 'commitContents'
-			// call.
-			CKEDITOR.tools.setTimeout( function(){
-				this.fire( 'saveSnapshot' );
-			}, 0, this );
+			// Check if it's a block element that accepts text.
+			if ( nextName && CKEDITOR.dtd.$block[ nextName ] && CKEDITOR.dtd[ nextName ]['#'] )
+				range.moveToElementEditStart( next );
 		}
+
+		selection.selectRanges( [ range ] );
+
+		if ( selIsLocked )
+			this.getSelection().lock();
 	}
 
@@ -967,6 +1030,7 @@
 						});
 
-					editor.on( 'insertHtml', onInsertHtml, null, null, 20 );
-					editor.on( 'insertElement', onInsertElement, null, null, 20 );
+					editor.on( 'insertHtml', onInsert( doInsertHtml ) , null, null, 20 );
+					editor.on( 'insertElement', onInsert( doInsertElement ), null, null, 20 );
+					editor.on( 'insertText', onInsert( doInsertText ), null, null, 20 );
 					// Auto fixing on some document structure weakness to enhance usabilities. (#3190 and #3189)
 					editor.on( 'selectionChange', onSelectionChangeFixBody, null, null, 1 );
