Index: /CKEditor/branches/features/fullpage/_samples/fullpage.html
===================================================================
--- /CKEditor/branches/features/fullpage/_samples/fullpage.html	(revision 4617)
+++ /CKEditor/branches/features/fullpage/_samples/fullpage.html	(revision 4617)
@@ -0,0 +1,41 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<!--
+Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>Full Page Editing - CKEditor Sample</title>
+	<script type="text/javascript" src="sample.js"></script>
+</head>
+<body>
+	<div id="html">
+		<form action="sample_posteddata.php" method="post">
+			<p>
+				In this sample the editor is configured to edit entire HTML pages, from the 
+				&lt;html&gt; tag to &lt;/html&gt;.</p>
+			<p>
+				<label for="editor1">
+					Editor 1:</label><br />
+				<textarea id="editor1" name="editor1" rows="10" cols="80">&lt;html&gt;&lt;head&gt;&lt;title&gt;CKEditor Sample&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;This is some &lt;strong&gt;sample text&lt;/strong&gt;. You are using &lt;a href="http://ckeditor.com/"&gt;CKEditor&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</textarea>
+				<script type="text/javascript">
+				//<![CDATA[
+
+					CKEDITOR.replace( 'editor1',
+						{
+							fullPage : true
+						});
+
+				//]]>
+				</script>
+			</p>
+			<p>
+				<input type="submit" value="Submit" />
+			</p>
+		</form>
+	</div>
+	<div id="code">
+		<pre></pre>
+	</div>
+</body>
+</html>
Index: /CKEditor/branches/features/fullpage/_samples/index.html
===================================================================
--- /CKEditor/branches/features/fullpage/_samples/index.html	(revision 4616)
+++ /CKEditor/branches/features/fullpage/_samples/index.html	(revision 4617)
@@ -19,4 +19,5 @@
 		<li><a href="replacebyclass.html">Replace textareas by class name</a></li>
 		<li><a href="replacebycode.html">Replace textareas by code</a></li>
+		<li><a href="fullpage.html">Full page support (editing from &lt;html&gt; to &lt;/html&gt;)</a></li>
 	</ul>
 	<h2>
Index: /CKEditor/branches/features/fullpage/_source/core/dom/element.js
===================================================================
--- /CKEditor/branches/features/fullpage/_source/core/dom/element.js	(revision 4616)
+++ /CKEditor/branches/features/fullpage/_source/core/dom/element.js	(revision 4617)
@@ -329,5 +329,7 @@
 		getHtml : function()
 		{
-			return this.$.innerHTML;
+			var retval = this.$.innerHTML;
+			// Strip <?xml:namespace> tags in IE. (#3341).
+			return CKEDITOR.env.ie ? retval.replace( /<\?[^>]*>/g, '' ) : retval;
 		},
 
Index: /CKEditor/branches/features/fullpage/_source/core/dtd.js
===================================================================
--- /CKEditor/branches/features/fullpage/_source/core/dtd.js	(revision 4616)
+++ /CKEditor/branches/features/fullpage/_source/core/dtd.js	(revision 4617)
@@ -33,5 +33,5 @@
 CKEDITOR.dtd = (function()
 {
-    var X = CKEDITOR.tools.extend,
+	var X = CKEDITOR.tools.extend,
 
 		A = {isindex:1,fieldset:1},
@@ -52,12 +52,20 @@
 		O = X({param:1},K),
 		P = X({form:1},A,D,E,I),
-		Q = {li:1};
+		Q = {li:1},
+		R = {style:1,script:1},
+		S = {base:1,link:1,meta:1,title:1},
+		T = X(S,R),
+		U = {head:1,body:1},
+		V = {html:1};
 
 	var block = {address:1,blockquote:1,center:1,dir:1,div:1,dl:1,fieldset:1,form:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,hr:1,isindex:1,menu:1,noframes:1,ol:1,p:1,pre:1,table:1,ul:1};
 
-    return /** @lends CKEDITOR.dtd */ {
+	return /** @lends CKEDITOR.dtd */ {
 
 		// The "$" items have been added manually.
 
+		// List of elements living outside body.
+		$nonBodyContent: X(V,U,S),
+
 		/**
 		 * List of block elements, like "p" or "div".
@@ -74,5 +82,5 @@
 		$blockLimit : { body:1,div:1,td:1,th:1,caption:1,form:1 },
 
-		$body : X({script:1}, block),
+		$body : X({script:1,style:1}, block),
 
 		$cdata : {script:1,style:1},
@@ -128,4 +136,13 @@
 		$tableContent : {caption:1,col:1,colgroup:1,tbody:1,td:1,tfoot:1,th:1,thead:1,tr:1},
 
+        html: U,
+        head: T,
+        style: N,
+        script: N,
+        body: P,
+        base: {},
+        link: {},
+        meta: {},
+        title: N,
         col : {},
         tr : {td:1,th:1},
Index: /CKEditor/branches/features/fullpage/_source/core/htmlparser/basicwriter.js
===================================================================
--- /CKEditor/branches/features/fullpage/_source/core/htmlparser/basicwriter.js	(revision 4616)
+++ /CKEditor/branches/features/fullpage/_source/core/htmlparser/basicwriter.js	(revision 4617)
@@ -118,4 +118,5 @@
 		{
 			this._.output = [];
+			this._.indent = false;
 		},
 
Index: /CKEditor/branches/features/fullpage/_source/core/htmlparser/cdata.js
===================================================================
--- /CKEditor/branches/features/fullpage/_source/core/htmlparser/cdata.js	(revision 4616)
+++ /CKEditor/branches/features/fullpage/_source/core/htmlparser/cdata.js	(revision 4617)
@@ -20,5 +20,4 @@
 		 */
 		this.value = value;
-
 	};
 
Index: /CKEditor/branches/features/fullpage/_source/core/htmlparser/element.js
===================================================================
--- /CKEditor/branches/features/fullpage/_source/core/htmlparser/element.js	(revision 4616)
+++ /CKEditor/branches/features/fullpage/_source/core/htmlparser/element.js	(revision 4617)
@@ -38,5 +38,5 @@
 
 	var dtd			= CKEDITOR.dtd,
-		isBlockLike	= !!( dtd.$block[ tagName ] || dtd.$listItem[ tagName ] || dtd.$tableContent[ tagName ] || dtd.$nonEditable[ tagName ] || tagName == 'br' ),
+		isBlockLike	= !!( dtd.$nonBodyContent[ tagName ] || dtd.$block[ tagName ] || dtd.$listItem[ tagName ] || dtd.$tableContent[ tagName ] || dtd.$nonEditable[ tagName ] || tagName == 'br' ),
 		isEmpty		= !!dtd.$empty[ name ];
 
Index: /CKEditor/branches/features/fullpage/_source/core/htmlparser/fragment.js
===================================================================
--- /CKEditor/branches/features/fullpage/_source/core/htmlparser/fragment.js	(revision 4616)
+++ /CKEditor/branches/features/fullpage/_source/core/htmlparser/fragment.js	(revision 4617)
@@ -115,5 +115,7 @@
 				else
 					elementName =  element.name;
-				if ( !( elementName in CKEDITOR.dtd.$body ) )
+				if ( elementName
+						&& !( elementName in CKEDITOR.dtd.$body )
+						&& !( elementName in CKEDITOR.dtd.$nonBodyContent )  )
 				{
 					var savedCurrent = currentNode;
@@ -180,14 +182,14 @@
 			}
 
-			var currentName = currentNode.name,
-				currentDtd = ( currentName && CKEDITOR.dtd[ currentName ] ) || ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span );
+			var currentName = currentNode.name;
+
+			var currentDtd = currentName
+				&& ( CKEDITOR.dtd[ currentName ]
+					|| ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span ) );
 
 			// If the element cannot be child of the current element.
-			if ( !element.isUnknown && !currentNode.isUnknown && !currentDtd[ tagName ] )
-			{
-				// If this is the fragment node, just ignore this tag and add
-				// its children.
-				if ( !currentName )
-					return;
+			if ( currentDtd   // Fragment could receive any elements.
+				 && !element.isUnknown && !currentNode.isUnknown && !currentDtd[ tagName ] )
+			{
 
 				var reApply = false,
@@ -331,4 +333,7 @@
 				}
 			}
+
+			if( tagName == 'body' )
+				fixForBody = false;
 		};
 
@@ -346,6 +351,10 @@
 			checkPending();
 
-			if ( fixForBody && !currentNode.type )
+			if ( fixForBody
+				 && ( !currentNode.type || currentNode.name == 'body' )
+				 && CKEDITOR.tools.trim( text ) )
+			{
 				this.onTagOpen( fixForBody, {} );
+			}
 
 			// Shrinking consequential spaces into one single for all elements
@@ -376,5 +385,7 @@
 				node = currentNode;
 
-			if ( fixForBody && !parent.type && !CKEDITOR.dtd.$body[ node.name ] )
+			if ( fixForBody
+				 && ( !parent.type || parent.name == 'body' )
+				 && !CKEDITOR.dtd.$body[ node.name ] )
 			{
 				currentNode = parent;
Index: /CKEditor/branches/features/fullpage/_source/plugins/htmldataprocessor/plugin.js
===================================================================
--- /CKEditor/branches/features/fullpage/_source/plugins/htmldataprocessor/plugin.js	(revision 4616)
+++ /CKEditor/branches/features/fullpage/_source/plugins/htmldataprocessor/plugin.js	(revision 4617)
@@ -110,5 +110,7 @@
 
 				// All "_cke" attributes are to be ignored.
-				[ ( /^_cke.*/ ), '' ]
+				[ ( /^_cke.*/ ), '' ],
+
+				[ 'hidefocus', '' ]
 			],
 
@@ -117,9 +119,13 @@
 				$ : function( element )
 				{
-					// Remove duplicated attributes - #3789.
 					var attribs = element.attributes;
 
 					if ( attribs )
 					{
+						// Elements marked as temporary are to be ignored.
+						if ( attribs.cke_temp )
+							return false;
+
+						// Remove duplicated attributes - #3789.
 						var attributeNames = [ 'name', 'href', 'src' ],
 							savedAttributeName;
@@ -130,4 +136,6 @@
 						}
 					}
+
+					return element;
 				},
 
@@ -163,4 +171,19 @@
 						return false;
 					}
+				},
+
+				body : function( element )
+				{
+					delete element.attributes.spellcheck;
+					delete element.attributes.contenteditable;
+				},
+
+				style : function( element )
+				{
+					var child = element.children[ 0 ];
+					child && child.value && ( child.value = CKEDITOR.tools.trim( child.value ));
+
+					if ( !element.attributes.type )
+						element.attributes.type = 'text/css';
 				}
 			},
@@ -210,4 +233,12 @@
 	var protectAttributeRegex = /<(?:a|area|img|input)[\s\S]*?\s((?:href|src|name)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+)))/gi;
 
+	var protectElementsRegex = /(?:<style(?=[ >])[^>]*>[\s\S]*<\/style>)|(?:<(:?link|meta|base)[^>]*>)/gi,
+		encodedElementsRegex = /<cke:encoded>([^<]*)<\/cke:encoded>/gi;
+
+	var protectElementNamesRegex = /(<\/?)((?:object|embed|param|html|body|head|title)[^>]*>)/gi,
+		unprotectElementNamesRegex = /(<\/?)cke:((?:html|body|head|title)[^>]*>)/gi;
+
+	var protectSelfClosingRegex = /<cke:(param|embed)([\s\S]*?)\/?>/gi;
+
 	function protectAttributes( html )
 	{
@@ -215,43 +246,33 @@
 	}
 
-	var protectStyleTagsRegex = /<(style)(?=[ >])[^>]*>[^<]*<\/\1>/gi;
-	var encodedTagsRegex = /<cke:encoded>([^<]*)<\/cke:encoded>/gi;
-	var protectElementNamesRegex = /(<\/?)((?:object|embed|param)[\s\S]*?>)/gi;
-	var protectSelfClosingRegex = /<cke:(param|embed)([\s\S]*?)\/?>/gi;
-
-	function protectStyleTagsMatch( match )
-	{
-		return '<cke:encoded>' + encodeURIComponent( match ) + '</cke:encoded>';
-	}
-
-	function protectStyleTags( html )
-	{
-		return html.replace( protectStyleTagsRegex, protectStyleTagsMatch );
-	}
+	function protectElements( html )
+	{
+		return html.replace( protectElementsRegex, function( match )
+			{
+				return '<cke:encoded>' + encodeURIComponent( match ) + '</cke:encoded>';
+			});
+	}
+
+	function unprotectElements( html )
+	{
+		return html.replace( encodedElementsRegex, function( match, encoded )
+			{
+				return decodeURIComponent( encoded );
+			});
+	}
+
 	function protectElementsNames( html )
 	{
 		return html.replace( protectElementNamesRegex, '$1cke:$2');
 	}
+
+	function unprotectElementNames( html )
+	{
+		return html.replace( unprotectElementNamesRegex, '$1$2' );
+	}
+
 	function protectSelfClosingElements( html )
 	{
 		return html.replace( protectSelfClosingRegex, '<cke:$1$2></cke:$1>' );
-	}
-
-	function unprotectEncodedTagsMatch( match, encoded )
-	{
-		return decodeURIComponent( encoded );
-	}
-
-	function unprotectEncodedTags( html )
-	{
-		return html.replace( encodedTagsRegex, unprotectEncodedTagsMatch );
-	}
-
-	function unprotectRealComments( html )
-	{
-		return html.replace( /<!--{cke_protected}{C}([\s\S]+?)-->/g, function( match, data )
-			{
-				return decodeURIComponent( data );
-			});
 	}
 
@@ -264,4 +285,12 @@
 						encodeURIComponent( match ).replace( /--/g, '%2D%2D' ) +
 						'-->';
+			});
+	}
+
+	function unprotectRealComments( html )
+	{
+		return html.replace( /<!--{cke_protected}{C}([\s\S]+?)-->/g, function( match, data )
+			{
+				return decodeURIComponent( data );
 			});
 	}
@@ -356,25 +385,27 @@
 			data = protectAttributes( data );
 
-			// IE remvoes style tags from innerHTML. (#3710).
-			if ( CKEDITOR.env.ie )
-				data = protectStyleTags( data );
+			// Protect elements than can't be set inside a DIV. E.g. IE removes
+			// style tags from innerHTML. (#3710)
+			data = protectElements( data );
 
 			// Certain elements has problem to go through DOM operation, protect
-			// them by prefixing 'cke' namespace.(#3591)
+			// them by prefixing 'cke' namespace. (#3591)
 			data = protectElementsNames( data );
 
 			// All none-IE browsers ignore self-closed custom elements,
-			// protecting them into open-close.(#3591)
+			// protecting them into open-close. (#3591)
 			data = protectSelfClosingElements( data );
 
 			// Call the browser to help us fixing a possibly invalid HTML
 			// structure.
-			var div = document.createElement( 'div' );
+			var div = new CKEDITOR.dom.element( 'div' );
 			// Add fake character to workaround IE comments bug. (#3801)
-			div.innerHTML = 'a' + data;
-			data = div.innerHTML.substr( 1 );
-
-			if ( CKEDITOR.env.ie )
-				data = unprotectEncodedTags( data );
+			div.setHtml( 'a' + data );
+			data = div.getHtml().substr( 1 );
+
+			// Unprotect "some" of the protected elements at this point.
+			data = unprotectElementNames( data );
+
+			data = unprotectElements( data );
 
 			// Restore the comments that have been protected, in this way they
Index: /CKEditor/branches/features/fullpage/_source/plugins/htmlwriter/plugin.js
===================================================================
--- /CKEditor/branches/features/fullpage/_source/plugins/htmlwriter/plugin.js	(revision 4616)
+++ /CKEditor/branches/features/fullpage/_source/plugins/htmlwriter/plugin.js	(revision 4617)
@@ -68,5 +68,5 @@
 		var dtd = CKEDITOR.dtd;
 
-		for ( var e in CKEDITOR.tools.extend( {}, dtd.$block, dtd.$listItem, dtd.$tableContent ) )
+		for ( var e in CKEDITOR.tools.extend( {}, dtd.$nonBodyContent, dtd.$block, dtd.$listItem, dtd.$tableContent ) )
 		{
 			this.setRules( e,
@@ -79,13 +79,27 @@
 				});
 		}
+
 		this.setRules( 'br',
 			{
 				breakAfterOpen : true
 			});
+
+		this.setRules( 'title',
+			{
+				indent : false,
+				breakAfterOpen : false
+			});
+
+		this.setRules( 'style',
+			{
+				indent : false,
+				breakBeforeClose : true
+			});
+
 		// Disable indentation on <pre>.
 		this.setRules( 'pre',
-		{
-		  indent: false
-		} );
+			{
+			  indent: false
+			});
 	},
 
@@ -263,5 +277,6 @@
 		 * </ul>
 		 *
-		 * All rules default to "false".
+		 * All rules default to "false". Each call to the function overrides
+		 * already present rules, leaving the undefined untouched.
 		 *
 		 * By default, all elements available in the {@link CKEDITOR.dtd.$block),
@@ -284,5 +299,10 @@
 		setRules : function( tagName, rules )
 		{
-			this._.rules[ tagName ] = rules;
+			var currentRules = this._.rules[ tagName ];
+			
+			if ( currentRules )
+				CKEDITOR.tools.extend( currentRules, rules, true );
+			else
+				this._.rules[ tagName ] = rules;
 		}
 	}
Index: /CKEditor/branches/features/fullpage/_source/plugins/wysiwygarea/plugin.js
===================================================================
--- /CKEditor/branches/features/fullpage/_source/plugins/wysiwygarea/plugin.js	(revision 4616)
+++ /CKEditor/branches/features/fullpage/_source/plugins/wysiwygarea/plugin.js	(revision 4617)
@@ -11,10 +11,9 @@
 (function()
 {
-	/**
-	 * List of elements in which has no way to move editing focus outside.
-	 */
+	// List of elements in which has no way to move editing focus outside.
 	var nonExitableElementNames = { table:1,pre:1 };
+
 	// Matching an empty paragraph at the end of document.
-	var emptyParagraphRegexp = /\s*<(p|div|address|h\d|center)[^>]*>\s*(?:<br[^>]*>|&nbsp;|\u00A0|&#160;)\s*(:?<\/\1>)?\s*$/gi;
+	var emptyParagraphRegexp = /\s*<(p|div|address|h\d|center)[^>]*>\s*(?:<br[^>]*>|&nbsp;|\u00A0|&#160;)\s*(:?<\/\1>)?\s*(?=$|<\/body>)/gi;
 
 	function onInsertHtml( evt )
@@ -583,26 +582,71 @@
 								isLoadingData = true;
 
+								var fullPage = editor.config.fullPage,
+									docType = editor.config.docType;
+
+								// Build the additional stuff to be included into <head>.
+								var headExtra = 
+									'<style type="text/css" cke_temp="1">' +
+										editor._.styles.join( '\n' ) +
+									'</style>';
+
+								!fullPage && ( headExtra =
+									'<link type="text/css" rel="stylesheet" href="' +
+									[].concat( editor.config.contentsCss ).join( '"><link type="text/css" rel="stylesheet" href="' ) +
+									'">' +
+									headExtra );
+
+								if ( fullPage )
+								{
+									// Search and sweep out the doctype declaration.
+									data = data.replace( /<!DOCTYPE[^>]*>/i, function( match )
+										{
+											editor.docType = docType = match;
+											return '';
+										});
+								}
+
 								// Get the HTML version of the data.
 								if ( editor.dataProcessor )
-								{
 									data = editor.dataProcessor.toHtml( data, fixForBody );
-								}
-
-								data =
-									editor.config.docType +
-									'<html dir="' + editor.config.contentsLangDirection + '">' +
-									'<head>' +
-										'<link type="text/css" rel="stylesheet" href="' +
-										[].concat( editor.config.contentsCss ).join( '"><link type="text/css" rel="stylesheet" href="' ) +
-										'">' +
-										'<style type="text/css" _fcktemp="true">' +
-											editor._.styles.join( '\n' ) +
-										'</style>'+
-									'</head>' +
-									'<body>' +
-										data +
-									'</body>' +
-									'</html>' +
-									activationScript;
+
+								if ( fullPage )
+								{
+									// Check if the <body> tag is available.
+									if ( !(/<body[\s|>]/).test( data ) )
+										data = '<body>' + data;
+
+									// Check if the <html> tag is available.
+									if ( !(/<html[\s|>]/).test( data ) )
+										data = '<html>' + data + '</html>';
+
+									// Check if the <head> tag is available.
+									if ( !(/<head[\s|>]/).test( data ) )
+										data = data.replace( /<html[^>]*>/, '$&<head><title></title></head>' ) ;
+
+									// Inject the extra stuff into <head>.
+									// Attention: do not change it before testing it well. (V2)
+									// This is tricky... if the head ends with <meta ... content type>,
+									// Firefox will break. But, it works if we place our extra stuff as
+									// the last elements in the HEAD.
+									data = data.replace( /<\/head\s*>/, headExtra + '$&' );
+
+									// Add the DOCTYPE back to it.
+									data = docType + data;
+								}
+								else
+								{
+									data =
+										editor.config.docType +
+										'<html dir="' + editor.config.contentsLangDirection + '">' +
+										'<head>' +
+											headExtra +
+										'</head>' +
+										'<body>' +
+											data
+										'</html>';
+								}
+								
+								data += activationScript;
 
 								CKEDITOR._[ 'contentDomReady' + editor.name ] = contentDomReady;
@@ -612,5 +656,12 @@
 							getData : function()
 							{
-								var data = iframe.getFrameDocument().getBody().getHtml();
+								var config = editor.config,
+									fullPage = config.fullPage,
+									docType = fullPage && editor.docType,
+									doc = iframe.getFrameDocument();
+								
+								var data = fullPage
+									? doc.getDocumentElement().getOuterHtml()
+									: doc.getBody().getHtml();
 
 								if ( editor.dataProcessor )
@@ -618,6 +669,9 @@
 
 								// Strip the last blank paragraph within document.
-								if ( editor.config.ignoreEmptyParagraph )
+								if ( config.ignoreEmptyParagraph )
 									data = data.replace( emptyParagraphRegexp, '' );
+
+								if ( docType )
+									data = docType + '\n' + data;
 
 								return data;
