Index: /CKEditor/trunk/CHANGES.html
===================================================================
--- /CKEditor/trunk/CHANGES.html	(revision 6460)
+++ /CKEditor/trunk/CHANGES.html	(revision 6461)
@@ -1,3 +1,3 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+﻿<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 <!--
 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
@@ -45,4 +45,5 @@
 			Fixed issues:</p>
 	<ul>
+		<li><a href="http://dev.ckeditor.com/ticket/1272">#1272</a> : It's now possible to apply styles to collapsed selections on Safari and Chrome (WebKit).</li>
 		<li><a href="http://dev.ckeditor.com/ticket/7054">#7054</a> : Special characters' tooltips are now lowercased, making them more readable.</li>
 		<li><a href="http://dev.ckeditor.com/ticket/7102">#7102</a> : "Replacing Div" sample didn't work when double clicking inside of formatted text.</li>
Index: /CKEditor/trunk/_source/core/dom/node.js
===================================================================
--- /CKEditor/trunk/_source/core/dom/node.js	(revision 6460)
+++ /CKEditor/trunk/_source/core/dom/node.js	(revision 6461)
@@ -205,27 +205,10 @@
 			{
 				var parentNode = node.parentNode;
-				var currentIndex = -1;
 
 				if ( parentNode )
 				{
-					for ( var i = 0 ; i < parentNode.childNodes.length ; i++ )
-					{
-						var candidate = parentNode.childNodes[i];
-
-						if ( normalized &&
-								candidate.nodeType == 3 &&
-								candidate.previousSibling &&
-								candidate.previousSibling.nodeType == 3 )
-						{
-							continue;
-						}
-
-						currentIndex++;
-
-						if ( candidate == node )
-							break;
-					}
-
-					address.unshift( currentIndex );
+					// Get the node index. For performance, call getIndex
+					// directly, instead of creating a new node object.
+					address.unshift( this.getIndex.call( { $ : node }, normalized ) );
 				}
 
@@ -248,22 +231,26 @@
 		},
 
-		getIndex : function()
-		{
-			var $ = this.$;
-
-			var currentNode = $.parentNode && $.parentNode.firstChild;
-			var currentIndex = -1;
-
-			while ( currentNode )
-			{
-				currentIndex++;
-
-				if ( currentNode == $ )
-					return currentIndex;
-
-				currentNode = currentNode.nextSibling;
-			}
-
-			return -1;
+		getIndex : function( normalized )
+		{
+			// Attention: getAddress depends on this.$
+
+			var current = this.$,
+				index = 0;
+
+			while ( ( current = current.previousSibling ) )
+			{
+				// When normalizing, do not count it if this is an
+				// empty text node or if it's a text node following another one.
+				if ( normalized && current.nodeType == 3 &&
+					 ( !current.nodeValue.length ||
+					   ( current.previousSibling && current.previousSibling.nodeType == 3 ) ) )
+				{
+					continue;
+				}
+
+				index++;
+			}
+
+			return index;
 		},
 
Index: /CKEditor/trunk/_source/core/dom/range.js
===================================================================
--- /CKEditor/trunk/_source/core/dom/range.js	(revision 6460)
+++ /CKEditor/trunk/_source/core/dom/range.js	(revision 6461)
@@ -590,4 +590,8 @@
 						startOffset = 0;
 					}
+
+					// Get the normalized offset.
+					if ( child && child.type == CKEDITOR.NODE_ELEMENT )
+						startOffset = child.getIndex( 1 );
 				}
 
@@ -618,4 +622,8 @@
 							endOffset = 0;
 						}
+
+						// Get the normalized offset.
+						if ( child && child.type == CKEDITOR.NODE_ELEMENT )
+							endOffset = child.getIndex( 1 );
 					}
 
Index: /CKEditor/trunk/_source/core/dom/text.js
===================================================================
--- /CKEditor/trunk/_source/core/dom/text.js	(revision 6460)
+++ /CKEditor/trunk/_source/core/dom/text.js	(revision 6461)
@@ -68,4 +68,9 @@
 		},
 
+		setText : function( text )
+		{
+			this.$.nodeValue = text;
+		},
+
 		/**
 		 * Breaks this text node into two nodes at the specified offset,
Index: /CKEditor/trunk/_source/plugins/editingblock/plugin.js
===================================================================
--- /CKEditor/trunk/_source/plugins/editingblock/plugin.js	(revision 6460)
+++ /CKEditor/trunk/_source/plugins/editingblock/plugin.js	(revision 6461)
@@ -154,4 +154,6 @@
 	CKEDITOR.editor.prototype.setMode = function( mode )
 	{
+		this.fire( 'beforeSetMode', { newMode : mode } );
+
 		var data,
 			holderElement = this.getThemeSpace( 'contents' ),
@@ -243,6 +245,14 @@
 
 /**
- * Fired before changing the editing mode
+ * Fired before changing the editing mode.
  * @name CKEDITOR.editor#beforeModeUnload
  * @event
  */
+
+ /**
+ * Fired before the editor mode is set.
+ * @name CKEDITOR.editor#beforeSetMode
+ * @event
+ * @since 3.5.3
+ * @param {String} newMode The name of the mode which is about to be set.
+ */
Index: /CKEditor/trunk/_source/plugins/selection/plugin.js
===================================================================
--- /CKEditor/trunk/_source/plugins/selection/plugin.js	(revision 6460)
+++ /CKEditor/trunk/_source/plugins/selection/plugin.js	(revision 6461)
@@ -100,8 +100,88 @@
 	};
 
+	function createFillingChar( doc )
+	{
+		removeFillingChar( doc );
+
+		var fillingChar = doc.createText( '\u200B' );
+		doc.setCustomData( 'cke-fillingChar', fillingChar );
+
+		return fillingChar;
+	}
+
+	function getFillingChar( doc )
+	{
+		return doc && doc.getCustomData( 'cke-fillingChar' );
+	}
+
+	// Checks if a filling char has been used, eventualy removing it (#1272).
+	function checkFillingChar( doc )
+	{
+		var fillingChar = doc && getFillingChar( doc );
+		if ( fillingChar )
+		{
+			// Use this flag to avoid removing the filling char right after
+			// creating it.
+			if ( fillingChar.getCustomData( 'ready' ) )
+				removeFillingChar( doc );
+			else
+				fillingChar.setCustomData( 'ready', 1 );
+		}
+	}
+
+	function removeFillingChar( doc )
+	{
+		var fillingChar = doc && doc.removeCustomData( 'cke-fillingChar' );
+		if ( fillingChar )
+		{
+			// We can't simply remove the filling node because the user
+			// will actually enlarge it when typing, so we just remove the
+			// invisible char from it.
+			fillingChar.setText( fillingChar.getText().replace( /\u200B/g, '' ) );
+			fillingChar = 0;
+		}
+	}
+
 	CKEDITOR.plugins.add( 'selection',
 	{
 		init : function( editor )
 		{
+			// On WebKit only, we need a special "filling" char on some situations
+			// (#1272). Here we set the events that should invalidate that char.
+			if ( CKEDITOR.env.webkit )
+			{
+				editor.on( 'selectionChange', function() { checkFillingChar( editor.document ); } );
+				editor.on( 'beforeSetMode', function() { removeFillingChar( editor.document ); } );
+				editor.on( 'key', function( e )
+					{
+						// Remove the filling char before some keys get
+						// executed, so they'll not get blocked by it.
+						switch ( e.data.keyCode )
+						{
+							case 37 :	// LEFT-ARROW
+							case 39 :	// RIGHT-ARROW
+							case 8 :	// BACKSPACE
+								removeFillingChar( editor.document );
+						}
+					});
+
+				var fillingCharBefore;
+				function beforeData()
+				{
+					var fillingChar = getFillingChar( editor.document );
+					fillingCharBefore = fillingChar && fillingChar.getText();
+					fillingCharBefore && fillingChar.setText( fillingCharBefore.replace( /\u200B/g, '' ) );
+				}
+				function afterData()
+				{
+						var fillingChar = getFillingChar( editor.document );
+						fillingChar && fillingChar.setText( fillingCharBefore );
+				}
+				editor.on( 'beforeUndoImage', beforeData );
+				editor.on( 'afterUndoImage', afterData );
+				editor.on( 'beforeGetData', beforeData, null, null, 0 );
+				editor.on( 'getData', afterData );
+			}
+
 			editor.on( 'contentDom', function()
 				{
@@ -1146,6 +1226,14 @@
 				var sel = this.getNative();
 
+				// getNative() returns null if iframe is "display:none" in FF. (#6577)
+				if ( !sel )
+					return;
+
 				if ( ranges.length )
+				{
 					sel.removeAllRanges();
+					// Remove any existing filling char first.
+					CKEDITOR.env.webkit && removeFillingChar( this.document );
+				}
 
 				for ( var i = 0 ; i < ranges.length ; i++ )
@@ -1197,6 +1285,44 @@
 					}
 
-					nativeRange.setStart( startContainer.$, range.startOffset );
-					nativeRange.setEnd( range.endContainer.$, range.endOffset );
+					if ( range.collapsed && CKEDITOR.env.webkit )
+					{
+						// Append a zero-width space so WebKit will not try to
+						// move the selection by itself (#1272).
+						var fillingChar = createFillingChar( this.document );
+						range.insertNode( fillingChar ) ;
+
+						var next = fillingChar.getNext();
+
+						// If the filling char is followed by a <br>, whithout
+						// having something before it, it'll not blink.
+						// Let's remove it in this case.
+						if ( next && !fillingChar.getPrevious() && next.type == CKEDITOR.NODE_ELEMENT && next.getName() == 'br' )
+						{
+							removeFillingChar( this.document );
+							range.moveToPosition( next, CKEDITOR.POSITION_BEFORE_START );
+						}
+						else
+							range.moveToPosition( fillingChar, CKEDITOR.POSITION_AFTER_END );
+					}
+
+					nativeRange.setStart( range.startContainer.$, range.startOffset );
+
+					try
+					{
+						nativeRange.setEnd( range.endContainer.$, range.endOffset );
+					}
+					catch ( e )
+					{
+						// There is a bug in Firefox implementation (it would be too easy
+						// otherwise). The new start can't be after the end (W3C says it can).
+						// So, let's create a new range and collapse it to the desired point.
+						if ( e.toString().indexOf( 'NS_ERROR_ILLEGAL_VALUE' ) >= 0 )
+						{
+							range.collapse( 1 );
+							nativeRange.setEnd( range.endContainer.$, range.endOffset );
+						}
+						else
+							throw e;
+					}
 
 					// Select the range.
@@ -1385,39 +1511,5 @@
 			function()
 			{
-				var startContainer = this.startContainer;
-
-				// If we have a collapsed range, inside an empty element, we must add
-				// something to it, otherwise the caret will not be visible.
-				if ( this.collapsed && startContainer.type == CKEDITOR.NODE_ELEMENT && !startContainer.getChildCount() )
-					startContainer.append( new CKEDITOR.dom.text( '' ) );
-
-				var nativeRange = this.document.$.createRange();
-				nativeRange.setStart( startContainer.$, this.startOffset );
-
-				try
-				{
-					nativeRange.setEnd( this.endContainer.$, this.endOffset );
-				}
-				catch ( e )
-				{
-					// There is a bug in Firefox implementation (it would be too easy
-					// otherwise). The new start can't be after the end (W3C says it can).
-					// So, let's create a new range and collapse it to the desired point.
-					if ( e.toString().indexOf( 'NS_ERROR_ILLEGAL_VALUE' ) >= 0 )
-					{
-						this.collapse( true );
-						nativeRange.setEnd( this.endContainer.$, this.endOffset );
-					}
-					else
-						throw( e );
-				}
-
-				var selection = this.document.getSelection().getNative();
-				// getSelection() returns null in case when iframe is "display:none" in FF. (#6577)
-				if ( selection )
-				{
-					selection.removeAllRanges();
-					selection.addRange( nativeRange );
-				}
+				this.document.getSelection().selectRanges( [ this ] );
 			};
 } )();
Index: /CKEditor/trunk/_source/plugins/undo/plugin.js
===================================================================
--- /CKEditor/trunk/_source/plugins/undo/plugin.js	(revision 6460)
+++ /CKEditor/trunk/_source/plugins/undo/plugin.js	(revision 6461)
@@ -149,4 +149,7 @@
 	{
 		this.editor = editor;
+
+		editor.fire( 'beforeUndoImage' );
+
 		var contents = editor.getSnapshot(),
 			selection	= contents && editor.getSelection();
@@ -157,4 +160,6 @@
 		this.contents	= contents;
 		this.bookmarks	= selection && selection.createBookmarks2( true );
+
+		editor.fire( 'afterUndoImage' );
 	};
 
@@ -553,2 +558,22 @@
  * @event
  */
+
+/**
+ * Fired before an undo image is to be taken. An undo image represents the
+ * editor state at some point. It's saved into an undo store, so the editor is
+ * able to recover the editor state on undo and redo operations.
+ * @name CKEDITOR.editor#beforeUndoImage
+ * @since 3.5.3
+ * @see CKEDITOR.editor#afterUndoImage
+ * @event
+ */
+
+/**
+ * Fired after an undo image is taken. An undo image represents the
+ * editor state at some point. It's saved into an undo store, so the editor is
+ * able to recover the editor state on undo and redo operations.
+ * @name CKEDITOR.editor#afterUndoImage
+ * @since 3.5.3
+ * @see CKEDITOR.editor#beforeUndoImage
+ * @event
+ */
