Index: src/wp-includes/js/mce-view.js
===================================================================
--- src/wp-includes/js/mce-view.js	(revision 28070)
+++ src/wp-includes/js/mce-view.js	(working copy)
@@ -41,7 +41,9 @@
 					doc = editor.getDoc();
 					$( doc ).find( '[data-wpview-text="' + this.encodedText + '"]' ).each(function (i, elem) {
 						var node = $( elem );
-						node.html( html );
+						// The <ins> is used to mark the end of the wrapper div. Needed when comparing
+						// the content as string for preventing extra undo levels.
+						node.html( html ).append( '<ins data-wpview-end="1"></ins>' );
 						$( self ).trigger( 'ready', elem );
 					});
 				}
@@ -93,6 +95,16 @@
 		},
 
 		/**
+		 * wp.mce.views.unbind( editor )
+		 *
+		 * The editor DOM is being rebuilt.
+		 * Fire an event for DOM cleanup for the passed TinyMCE instance.
+		 */
+		unbind: function( editor ) {
+			// fire event?
+		},
+
+		/**
 		 * toViews( content )
 		 * Scans a `content` string for each view's pattern, replacing any
 		 * matches with wrapper elements, and creates a new instance for
Index: src/wp-includes/js/tinymce/plugins/wpview/plugin.js
===================================================================
--- src/wp-includes/js/tinymce/plugins/wpview/plugin.js	(revision 28070)
+++ src/wp-includes/js/tinymce/plugins/wpview/plugin.js	(working copy)
@@ -80,11 +80,12 @@
 			'contenteditable': 'true'
 		}, getViewText( viewNode ) );
 
-		viewNode.appendChild( clipboard );
+		// Prepend inside the wrapper
+		viewNode.insertBefore( clipboard, viewNode.firstChild );
 
 		// Both of the following are necessary to prevent manipulating the selection/focus
-		editor.dom.bind( clipboard, 'beforedeactivate focusin focusout', _stop );
-		editor.dom.bind( selected, 'beforedeactivate focusin focusout', _stop );
+		dom.bind( clipboard, 'beforedeactivate focusin focusout', _stop );
+		dom.bind( selected, 'beforedeactivate focusin focusout', _stop );
 
 		// Make sure that the editor is focused.
 		// It is possible that the editor is not focused when the mouse event fires
@@ -140,8 +141,12 @@
 		return;
 	}
 
+	function emptyViews( content ) {
+		return content.replace(/(<div[^>]+wpview-wrap[^>]+>)[\s\S]+?data-wpview-end[^>]*><\/ins><\/div>/g, '$1</div>' );
+	}
+
 	editor.on( 'BeforeAddUndo', function( event ) {
-		if ( selected && ! toRemove ) {
+		if ( event.lastLevel && emptyViews( event.level.content ) === emptyViews( event.lastLevel.content ) ) {
 			event.preventDefault();
 		}
 	});
@@ -149,12 +154,16 @@
 	// When the editor's content changes, scan the new content for
 	// matching view patterns, and transform the matches into
 	// view wrappers.
-	editor.on( 'BeforeSetContent', function( e ) {
-		if ( ! e.content ) {
+	editor.on( 'BeforeSetContent', function( event ) {
+		if ( ! event.content ) {
 			return;
 		}
 
-		e.content = wp.mce.views.toViews( e.content );
+		if ( ! event.initial ) {
+			wp.mce.views.unbind( editor );
+		}
+
+		event.content = wp.mce.views.toViews( event.content );
 	});
 
 	// When the editor's content has been updated and the DOM has been
@@ -162,11 +171,7 @@
 	editor.on( 'SetContent', function( event ) {
 		var body, padNode;
 
-		// don't (re-)render views if the format of the content is raw
-		// to avoid adding additional undo levels on undo/redo
-		if ( event.format !== 'raw' ) {
-			wp.mce.views.render();
-		}
+		wp.mce.views.render();
 
 		// Add padding <p> if the noneditable node is last
 		if ( event.load || ! event.set ) {
