Index: _source/core/config.js
===================================================================
--- _source/core/config.js	(revision 3218)
+++ _source/core/config.js	(working copy)
@@ -154,6 +154,15 @@
 	plugins : 'basicstyles,blockquote,button,clipboard,colorbutton,contextmenu,elementspath,enterkey,entities,find,flash,font,format,forms,horizontalrule,htmldataprocessor,image,indent,justify,keystrokes,link,list,newpage,pagebreak,pastefromword,pastetext,preview,print,removeformat,save,smiley,showblocks,sourcearea,stylescombo,table,specialchar,tab,templates,toolbar,undo,wysiwygarea,wsc',
 
 	/**
+	 * The editor tabindex value.
+	 * @type Number
+	 * @default 0 (zero)
+	 * @example
+	 * config.tabIndex = 1;
+	 */
+	tabIndex : 0,
+
+	/**
 	 * The theme to be used to build the UI.
 	 * @type String
 	 * @default 'default'
Index: _source/core/dom/element.js
===================================================================
--- _source/core/dom/element.js	(revision 3218)
+++ _source/core/dom/element.js	(working copy)
@@ -732,6 +732,17 @@
 		},
 
 		/**
+		 * Checks if this element is visible. May not work if the element is
+		 * child of an element with visibility set to "hidden", but works well
+		 * on the great majority of cases.
+		 * @return {Boolean} True if the element is visible.
+		 */
+		isVisible : function()
+		{
+			return this.$.offsetWidth && ( this.$.style.visibility != 'hidden' );
+		},
+
+		/**
 		 * Indicates that the element has defined attributes.
 		 * @returns {Boolean} True if the element has attributes.
 		 * @example
@@ -883,8 +894,10 @@
 				{
 					if ( name == 'class' )
 						this.$.className = value;
-					if ( name == 'style' )
+					else if ( name == 'style' )
 						this.$.style.cssText = value;
+					else if ( name == 'tabindex' )	// Case sensitive.
+						this.$.tabIndex = value;
 					else
 						standard.apply( this, arguments );
 					return this;
Index: _source/core/dom/node.js
===================================================================
--- _source/core/dom/node.js	(revision 3218)
+++ _source/core/dom/node.js	(working copy)
@@ -308,25 +308,22 @@
 
 		getPreviousSourceNode : function( startFromSibling, nodeType )
 		{
-			var $ = startFromSibling ? this.$.previousSibling : this.$,
-				node = null;
+			var $ = this.$;
 
-			if ( !$ )
-				return null;
+			var node = ( !startFromSibling && $.lastChild ) ?
+				$.lastChild :
+				$.previousSibling;
 
-			if ( ( node = $.previousSibling ) )
-			{
-				while ( node.lastChild )
-					node = node.lastChild;
-			}
-			else
-				node = $.parentNode;
+			var parent;
 
+			while ( !node && ( parent = ( parent || $ ).parentNode ) )
+				node = parent.previousSibling;
+
 			if ( !node )
 				return null;
 
 			if ( nodeType && node.nodeType != nodeType )
-				return arguments.callee.apply( { $ : node }, false, nodeType );
+				return arguments.callee.call( { $ : node }, false, nodeType );
 
 			return new CKEDITOR.dom.node( node );
 		},
Index: _source/plugins/editingblock/plugin.js
===================================================================
--- _source/plugins/editingblock/plugin.js	(revision 3218)
+++ _source/plugins/editingblock/plugin.js	(working copy)
@@ -98,6 +98,12 @@
 					// Do that once only.
 					event.removeListener();
 
+					// Grab editor focus if the editor container is focused. (#3104)
+					editor.container.on( 'focus', function()
+						{
+							editor.focus();
+						});
+
 					// Fire instanceReady for both the editor and CKEDITOR.
 					editor.fireOnce( 'instanceReady' );
 					CKEDITOR.fire( 'instanceReady', null, editor );
Index: _source/plugins/sourcearea/plugin.js
===================================================================
--- _source/plugins/sourcearea/plugin.js	(revision 3218)
+++ _source/plugins/sourcearea/plugin.js	(working copy)
@@ -26,7 +26,11 @@
 						{
 							// Create the source area <textarea>.
 							textarea = new CKEDITOR.dom.element( 'textarea' );
-							textarea.setAttribute( 'dir', 'ltr' );
+							textarea.setAttributes(
+								{
+									dir : 'ltr',
+									tabIndex : -1
+								});
 							textarea.addClass( 'cke_source' );
 							textarea.setStyles({
 								width	: '100%',
@@ -35,11 +39,6 @@
 								outline	: 'none',
 								'text-align' : 'left' });
 
-							// Add the tab index for #3098.
-							var tabIndex = editor.element && editor.element.getAttribute( 'tabIndex' );
-							if ( tabIndex )
-								textarea.setAttribute( 'tabIndex', tabIndex );
-
 							// The textarea height/width='100%' doesn't
 							// constraint to the 'td' in IE strick mode
 							if ( CKEDITOR.env.ie )
@@ -74,6 +73,10 @@
 							// Set the <textarea> value.
 							this.loadData( data );
 
+							var keystrokeHandler = editor.keystrokeHandler;
+							if ( keystrokeHandler )
+								keystrokeHandler.attach( textarea );
+
 							editor.mode = 'source';
 							editor.fire( 'mode' );
 						},
Index: _source/plugins/tab/plugin.js
===================================================================
--- _source/plugins/tab/plugin.js	(revision 3218)
+++ _source/plugins/tab/plugin.js	(working copy)
@@ -5,41 +5,11 @@
 
 (function()
 {
-	var blurInternal = function( editor, previous )
-	{
-		var hasContainer = editor.container;
-
-		if ( hasContainer )
-		{
-			// We need an empty element after the container, so the focus don't go to a container child.
-			var tempSpan = new CKEDITOR.dom.element( 'span' );
-			tempSpan.setAttribute( 'tabindex', editor.container.getTabIndex() );
-			tempSpan.hide();
-
-			// Insert the temp element and set the focus.
-			if ( previous )
-			{
-				tempSpan.insertBefore( editor.container );
-				tempSpan.focusPrevious();
-			}
-			else
-			{
-				tempSpan.insertAfter( editor.container );
-				tempSpan.focusNext();
-			}
-
-			// Remove the temporary node.
-			tempSpan.remove();
-		}
-
-		return hasContainer;
-	};
-
 	var blurCommand =
 		{
 			exec : function( editor )
 			{
-				return blurInternal( editor );
+				editor.container.focusNext( true );
 			}
 		};
 
@@ -47,7 +17,7 @@
 		{
 			exec : function( editor )
 			{
-				return blurInternal( editor, true );
+				editor.container.focusPrevious( true );
 			}
 		};
 
@@ -122,62 +92,72 @@
  * var element = CKEDITOR.document.getById( 'example' );
  * element.focusNext();
  */
-CKEDITOR.dom.element.prototype.focusNext = function()
+CKEDITOR.dom.element.prototype.focusNext = function( ignoreChildren )
 {
 	var $ = this.$,
 		curTabIndex = this.getTabIndex(),
-		passedCurrent = false,
-		elected,
-		electedTabIndex;
+		passedCurrent, enteredCurrent,
+		elected, electedTabIndex,
+		element, elementTabIndex;
 
-	var all = document.body.all || document.body.getElementsByTagName( '*' );
-
 	if ( curTabIndex <= 0 )
 	{
-		for ( var i = 0, element ; element = all[ i ] ; i++ )
-		{
-			if ( !passedCurrent )
-			{
-				if ( element == $ )
-					passedCurrent = true;
-				continue;
-			}
+		// If this element has tabindex <= 0 then we must simply look for any
+		// element following it containing tabindex=0.
 
-			element = new CKEDITOR.dom.element( element );
+		var element = this.getNextSourceNode( ignoreChildren, CKEDITOR.NODE_ELEMENT );
 
-			if ( element.getComputedStyle( 'display' ) == 'none' || element.getComputedStyle( 'visibility' ) == 'hidden' )
-				continue;
-
-			if ( element.getTabIndex() === 0 )
+		while( element )
+		{
+			if ( element.isVisible() && element.getTabIndex() === 0 )
 			{
 				elected = element;
 				break;
 			}
+
+			element = element.getNextSourceNode( false, CKEDITOR.NODE_ELEMENT );
 		}
 	}
 	else
 	{
-		for ( i = 0, element ; element = all[ i ] ; i++ )
+		// If this element has tabindex > 0 then we must look for:
+		//		1. An element following this element with the same tabindex.
+		//		2. The first element in source other with the lowest tabindex
+		//		   that is higher than this element tabindex.
+		//		3. The first element with tabindex=0.
+
+		var element = this.getDocument().getBody().getFirst();
+
+		while( ( element = element.getNextSourceNode( false, CKEDITOR.NODE_ELEMENT ) ) )
 		{
-			if ( !passedCurrent && element == $ )
+			if ( !passedCurrent )
 			{
-				passedCurrent = true;
-				continue;
+				if ( !enteredCurrent && element.equals( this ) )
+				{
+					enteredCurrent = true;
+
+					// Ignore this element, if required.
+					if ( ignoreChildren )
+					{
+						if ( !( element = element.getNextSourceNode( true, CKEDITOR.NODE_ELEMENT ) ) )
+							break;
+						passedCurrent = 1;
+					}
+				}
+				else if ( enteredCurrent && !this.contains( element ) )
+					passedCurrent = 1;
 			}
 
-			element = new CKEDITOR.dom.element( element );
-
-			if ( element.getComputedStyle( 'display' ) == 'none' || element.getComputedStyle( 'visibility' ) == 'hidden' )
+			if ( !element.isVisible() || ( elementTabIndex = element.getTabIndex() ) < 0 )
 				continue;
 
-			var elementTabIndex = element.getTabIndex();
-
 			if ( passedCurrent && elementTabIndex == curTabIndex )
 			{
 				elected = element;
 				break;
 			}
-			else if ( elementTabIndex > curTabIndex && ( !elected || electedTabIndex > elementTabIndex || electedTabIndex === 0 ) )
+
+			if ( elementTabIndex > curTabIndex && ( !elected || !electedTabIndex || elementTabIndex < electedTabIndex ) )
 			{
 				elected = element;
 				electedTabIndex = elementTabIndex;
@@ -200,69 +180,75 @@
  * var element = CKEDITOR.document.getById( 'example' );
  * element.focusPrevious();
  */
-CKEDITOR.dom.element.prototype.focusPrevious = function()
+CKEDITOR.dom.element.prototype.focusPrevious = function( ignoreChildren )
 {
 	var $ = this.$,
 		curTabIndex = this.getTabIndex(),
-		passedCurrent = false,
+		passedCurrent, enteredCurrent,
 		elected,
-		electedTabIndex;
+		electedTabIndex = 0,
+		elementTabIndex;
 
-	var all = document.body.all || document.body.getElementsByTagName( '*' );
+	var element = this.getDocument().getBody().getLast();
 
-	if ( curTabIndex <= 0 )
+	while( ( element = element.getPreviousSourceNode( false, CKEDITOR.NODE_ELEMENT ) ) )
 	{
-		for ( var i = 0, element ; element = all[ i ] ; i++ )
+		if ( !passedCurrent )
 		{
-			if ( !passedCurrent && element == $ )
+			if ( !enteredCurrent && element.equals( this ) )
 			{
-				if ( elected && electedTabIndex === 0 )
-					break;
+				enteredCurrent = true;
 
-				passedCurrent = true;
-				continue;
+				// Ignore this element, if required.
+				if ( ignoreChildren )
+				{
+					if ( !( element = element.getPreviousSourceNode( true, CKEDITOR.NODE_ELEMENT ) ) )
+						break;
+					passedCurrent = 1;
+				}
 			}
+			else if ( enteredCurrent && !this.contains( element ) )
+				passedCurrent = 1;
+		}
 
-			element = new CKEDITOR.dom.element( element );
+		if ( !element.isVisible() || ( elementTabIndex = element.getTabIndex() ) < 0 )
+			continue;
 
-			if ( element.getComputedStyle( 'display' ) == 'none' || element.getComputedStyle( 'visibility' ) == 'hidden' )
-				continue;
+		if ( curTabIndex <= 0 )
+		{
+			// If this element has tabindex <= 0 then we must look for:
+			//		1. An element before this one containing tabindex=0.
+			//		2. The last element with the highest tabindex.
 
-			var elementTabIndex = element.getTabIndex();
+			if ( passedCurrent && elementTabIndex === 0 )
+			{
+				elected = element;
+				break;
+			}
 
-			if ( ( !passedCurrent && elementTabIndex === 0 )
-				|| ( elementTabIndex > 0 && ( !elected || ( electedTabIndex > 0 && electedTabIndex <= elementTabIndex ) ) ) )
+			if ( elementTabIndex > electedTabIndex )
 			{
 				elected = element;
 				electedTabIndex = elementTabIndex;
 			}
 		}
-	}
-	else
-	{
-		for ( i = 0, element ; element = all[ i ] ; i++ )
+		else
 		{
-			if ( !passedCurrent && element == $ )
-			{
-				if ( elected && electedTabIndex == curTabIndex )
-					break;
+			// If this element has tabindex > 0 we must look for:
+			//		1. An element preceeding this one, with the same tabindex.
+			//		2. The last element in source other with the highest tabindex
+			//		   that is lower than this element tabindex.
 
-				passedCurrent = true;
-				continue;
+			if ( passedCurrent && elementTabIndex == curTabIndex )
+			{
+				elected = element;
+				break;
 			}
 
-			element = new CKEDITOR.dom.element( element );
-
-			elementTabIndex = element.getTabIndex();
-
-			if ( elementTabIndex > 0 )
+			if ( elementTabIndex < curTabIndex && ( !elected || elementTabIndex > electedTabIndex ) )
 			{
-				if ( ( !passedCurrent && elementTabIndex == curTabIndex )
-					|| ( elementTabIndex < curTabIndex && ( !elected || electedTabIndex <= elementTabIndex ) ) )
-				{
-					elected = element;
-					electedTabIndex = elementTabIndex;
-				}
+				elected = element;
+				electedTabIndex = elementTabIndex;
 			}
 		}
 	}
Index: _source/plugins/wysiwygarea/plugin.js
===================================================================
--- _source/plugins/wysiwygarea/plugin.js	(revision 3218)
+++ _source/plugins/wysiwygarea/plugin.js	(working copy)
@@ -116,15 +116,12 @@
 						iframe = new CKEDITOR.dom.element( 'iframe' )
 							.setAttributes({
 								frameBorder : 0,
+								tabIndex : -1,
 								allowTransparency : true })
 							.setStyles({
 								width : '100%',
 								height : '100%' });
 
-						var tabIndex = editor.element && editor.element.getAttribute( 'tabIndex' );
-						if ( tabIndex )
-							iframe.setAttribute( 'tabIndex', tabIndex );
-
 						if ( CKEDITOR.env.ie )
 						{
 							if ( isCustomDomain )
Index: _source/themes/default/theme.js
===================================================================
--- _source/themes/default/theme.js	(revision 3218)
+++ _source/themes/default/theme.js	(working copy)
@@ -46,6 +46,8 @@
 			var height	= contentsHtml && editor.config.height;
 			var width	= editor.config.width;
 
+			var tabIndex = editor.config.tabIndex || editor.element.getAttribute( 'tabindex' ) || 0;
+
 			// The editor height is considered only if the contents space got filled.
 			if ( !contentsHtml )
 				height = 'auto';
@@ -65,8 +67,13 @@
 			// bring any evident problem as it seems that tables are treated
 			// differently by the browsers ("semi-inline").
 			var container = CKEDITOR.dom.element.createFromHtml( [
-				'<span id="cke_', name, '" onmousedown="return false;" class="', editor.skinClass,
-					'" dir="', editor.lang.dir, '" title="', ( CKEDITOR.env.gecko ? ' ' : '' ), '">' +
+				'<span' +
+					' id="cke_', name, '"' +
+					' onmousedown="return false;"' +
+					' class="', editor.skinClass, '"' +
+					' dir="', editor.lang.dir, '"' +
+					' title="', ( CKEDITOR.env.gecko ? ' ' : '' ), '"' +
+					' tabindex="' + tabIndex + '">' +
 				'<span class="' , browserCssClass, ' cke_', editor.lang.dir, '">' +
 					'<table class="cke_editor" border="0" cellspacing="0" cellpadding="0" style="width:', width, ';height:', height, '"><tbody>' +
 						'<tr', topHtml		? '' : ' style="display:none"', '><td id="cke_top_'		, name, '" class="cke_top">'		, topHtml		, '</td></tr>' +
