Index: src/wp-includes/js/mce-view.js
===================================================================
--- src/wp-includes/js/mce-view.js	(revision 31485)
+++ src/wp-includes/js/mce-view.js	(working copy)
@@ -1,34 +1,234 @@
 /* global tinymce */
-/**
+
+/*
+ * The TinyMCE view API.
+ *
  * Note: this API is "experimental" meaning that it will probably change
  * in the next few releases based on feedback from 3.9.0.
  * If you decide to use it, please follow the development closely.
  */
-
-// Ensure the global `wp` object exists.
-window.wp = window.wp || {};
-
 ( function( $ ) {
 	'use strict';
 
 	var views = {},
 		instances = {},
-		media = wp.media,
-		mediaWindows = [],
-		windowIdx = 0,
-		waitInterval = 50,
-		viewOptions = ['encodedText'];
+		viewOptions = [ 'encodedText' ];
 
-	// Create the `wp.mce` object if necessary.
+	window.wp = window.wp || {};
 	wp.mce = wp.mce || {};
 
 	/**
-	 * wp.mce.View
+	 * wp.mce.views
 	 *
+	 * A set of utilities that simplifies adding custom UI within a TinyMCE editor.
+	 * At its core, it serves as a series of converters, transforming text to a
+	 * custom UI, and back again.
+	 */
+	wp.mce.views = {
+
+		/**
+		 * Registers a new TinyMCE view.
+		 *
+		 * @param {String} The view type.
+		 * @param {Object} A constructor.
+		 */
+		register: function( type, constructor ) {
+			var defaultConstructor = {
+					type: type,
+					View: {},
+					toView: function( content ) {
+						var match = wp.shortcode.next( this.type, content );
+
+						if ( ! match ) {
+							return;
+						}
+
+						return {
+							index: match.index,
+							content: match.content,
+							options: {
+								shortcode: match.shortcode
+							}
+						};
+					}
+				};
+
+			constructor = _.defaults( constructor, defaultConstructor );
+			constructor.View = wp.mce.View.extend( constructor.View );
+
+			views[ type ] = constructor;
+		},
+
+		/**
+		 * Unregisters a TinyMCE view.
+		 *
+		 * @param {String} The view type.
+		 */
+		unregister: function( type ) {
+			delete views[ type ];
+		},
+
+		/**
+		 * Returns a TinyMCE view constructor.
+		 *
+		 * @param {String} The view type.
+		 */
+		get: function( type ) {
+			return views[ type ];
+		},
+
+		/**
+		 * The editor DOM is being rebuilt, run cleanup.
+		 */
+		unbind: function() {
+			_.each( instances, function( instance ) {
+				instance.unbind();
+			} );
+		},
+
+		/**
+		 * Scans a given string for each view's pattern,
+		 * replacing any matches with markers,
+		 * and creates a new instance for every match,
+		 * which triggers the related data to be fetched.
+		 *
+		 * @param {String} String to scan.
+		 */
+		toViews: function( content ) {
+			var pieces = [ { content: content } ],
+				current;
+
+			_.each( views, function( view, type ) {
+				current = pieces.slice();
+				pieces  = [];
+
+				_.each( current, function( piece ) {
+					var remaining = piece.content,
+						result;
+
+					// Ignore processed pieces, but retain their location.
+					if ( piece.processed ) {
+						pieces.push( piece );
+						return;
+					}
+
+					// Iterate through the string progressively matching views
+					// and slicing the string as we go.
+					while ( remaining && ( result = view.toView( remaining ) ) ) {
+						// Any text before the match becomes an unprocessed piece.
+						if ( result.index ) {
+							pieces.push( { content: remaining.substring( 0, result.index ) } );
+						}
+
+						// Add the processed piece for the match.
+						pieces.push( {
+							content: wp.mce.views.toView( type, result.content, result.options ),
+							processed: true
+						} );
+
+						// Update the remaining content.
+						remaining = remaining.slice( result.index + result.content.length );
+					}
+
+					// There are no additional matches. If any content remains,
+					// add it as an unprocessed piece.
+					if ( remaining ) {
+						pieces.push( { content: remaining } );
+					}
+				});
+			});
+
+			return _.pluck( pieces, 'content' ).join('');
+		},
+
+		/**
+		 * Returns the marked text for a particular view type,
+		 * and creates a new instance for that view type if it does not exist.
+		 * The text will be returned unmarked if no view of that type is registered.
+		 *
+		 * @param {String} The view type.
+		 * @param {String} The textual representation of the view.
+		 * @param {Object} Options.
+		 *
+		 * @return {String} The markered text.
+		 */
+		toView: function( type, text, options ) {
+			var view = this.get( type ),
+				encodedText = window.encodeURIComponent( text );
+
+			if ( ! view ) {
+				return text;
+			}
+
+			if ( ! this.getInstance( encodedText ) ) {
+				instances[ encodedText ] = new view.View( _.extend( options, {
+					encodedText: encodedText,
+					type: type
+				} ) );
+			}
+
+			return '<p data-wpview-marker="' + encodedText + '">' + text + '</p>';
+		},
+
+		/**
+		 * Refresh views after an update is made.
+		 *
+		 * @param {Object} The view to refresh.
+		 * @param {String} The textual representation of the view.
+		 * @param {Boolean} Whether or not to force rendering.
+		 */
+		refreshView: function( view, text, force ) {
+			var encodedText = window.encodeURIComponent( text );
+
+			if ( ! this.getInstance( encodedText ) ) {
+				instances[ encodedText ] = new view.View( _.extend( view.toView( text ).options, {
+					encodedText: encodedText,
+					type: view.type
+				} ) );
+			}
+
+			instances[ encodedText ].render( force );
+		},
+
+		/**
+		 * @param {String} The textual representation of the view, encoded.
+		 */
+		getInstance: function( encodedText ) {
+			return instances[ encodedText ];
+		},
+
+		/**
+		 * Renders any view instances.
+		 *
+		 * View instances are detected by the presence of markers.
+		 * To generate markers, pass your content through `toViews`.
+		 *
+		 * @param {Boolean} Whether or not to force rendering.
+		 */
+		render: function( force ) {
+			_.each( instances, function( instance ) {
+				instance.render( force );
+			} );
+		},
+
+		/**
+		 * @param {HTMLElement} A view node.
+		 */
+		edit: function( node ) {
+			var type = $( node ).data( 'wpview-type' ),
+				view = this.get( type );
+
+			if ( view ) {
+				view.edit( node );
+			}
+		}
+	};
+
+	/**
 	 * A Backbone-like View constructor intended for use when rendering a TinyMCE View. The main difference is
 	 * that the TinyMCE View is not tied to a particular DOM node.
 	 *
-	 * @param {Object} [options={}]
+	 * @param {Object} Options.
 	 */
 	wp.mce.View = function( options ) {
 		options = options || {};
@@ -38,100 +238,231 @@
 	};
 
 	_.extend( wp.mce.View.prototype, {
+
+		/**
+		 * Whether or not to display a loader.
+		 *
+		 * @type {Boolean}
+		 */
+		loader: true,
+
+		/**
+		 * Runs after the view instance is created.
+		 */
 		initialize: function() {},
+
+		/**
+		 * Retuns the HTML string to render in the view.
+		 *
+		 * @return {String} The HTML string.
+		 */
 		getHtml: function() {
 			return '';
 		},
-		loadingPlaceholder: function() {
-			return '' +
-				'<div class="loading-placeholder">' +
-					'<div class="dashicons dashicons-admin-media"></div>' +
-					'<div class="wpview-loading"><ins></ins></div>' +
-				'</div>';
-		},
-		render: function( force ) {
-			if ( force || ! this.rendered() ) {
-				this.unbind();
 
-				this.setContent(
-					'<p class="wpview-selection-before">\u00a0</p>' +
-					'<div class="wpview-body" contenteditable="false">' +
-						'<div class="toolbar mce-arrow-down">' +
-							( _.isFunction( views[ this.type ].edit ) ? '<div class="dashicons dashicons-edit edit"></div>' : '' ) +
-							'<div class="dashicons dashicons-no remove"></div>' +
-						'</div>' +
-						'<div class="wpview-content wpview-type-' + this.type + '">' +
-							( this.getHtml() || this.loadingPlaceholder() ) +
-						'</div>' +
-						( this.overlay ? '<div class="wpview-overlay"></div>' : '' ) +
-					'</div>' +
-					'<p class="wpview-selection-after">\u00a0</p>',
-					'wrap'
-				);
+		/**
+		 * Retuns the HTML string to render in the view.
+		 *
+		 * @param {Boolean} Whether or not to rerender.
+		 */
+		render: function( rerender ) {
+			var i = 0;
 
-				$( this ).trigger( 'ready' );
+			// If there's nothing to render an no loader needs to be shown, stop.
+			if ( ! this.loader && ! this.getHtml() ) {
+				return;
+			}
+
+			// We're about to rerender all views of this instance, so unbind rendered views.
+			rerender && this.unbind();
+
+			// Replace any left over markers.
+			this.replaceMarkers();
 
-				this.rendered( true );
+			if ( this.getHtml() ) {
+				this.setContent( this.getHtml(), function( editor, node ) {
+					$( node ).data( 'rendered', true );
+					this.bindNodes.apply( this, arguments );
+					i++;
+				}, rerender ? null : false );
+
+				// Use `bindNodes` if possible.
+				i && $( this ).trigger( 'ready' );
+			} else {
+				this.setLoader();
 			}
 		},
-		unbind: function() {},
+
+		/**
+		 * Runs after a view is added to the DOM.
+		 *
+		 * @param {tinymce.Editor} The editor instance the view node is in.
+		 * @param {HTMLElement} The view node.
+		 * @param {HTMLElement} The view's content node.
+		 */
+		bindNodes: function() {},
+
+		/**
+		 * Runs before a view is removed from the DOM.
+		 */
+		unbind: function() {
+			this.getNodes( function() {
+				this.unbindNodes.apply( this, arguments );
+			}, true );
+		},
+
+		/**
+		 * Runs before a view is removed from the DOM.
+		 *
+		 * @param {tinymce.Editor} The editor instance the view node is in.
+		 * @param {HTMLElement} The view node.
+		 * @param {HTMLElement} The view's content node.
+		 */
+		unbindNodes: function() {},
+
+		/**
+		 * Gets all the TinyMCE editors that support views.
+		 *
+		 * @param {Function} A callback (optional).
+		 *
+		 * @return {tinymce.Editor[]} An array of TinyMCE editor instances.
+		 */
 		getEditors: function( callback ) {
 			var editors = [];
 
 			_.each( tinymce.editors, function( editor ) {
 				if ( editor.plugins.wpview ) {
-					if ( callback ) {
-						callback( editor );
-					}
-
 					editors.push( editor );
+					callback && callback.call( this, editor );
 				}
 			}, this );
 
 			return editors;
 		},
-		getNodes: function( callback ) {
-			var nodes = [],
-				self = this;
+
+		/**
+		 * Gets all the view nodes in each editor.
+		 *
+		 * @param {Function} A callback (optional).
+		 * @param {Boolean} Get (un)rendered nodes (optional).
+		 *
+		 * @return {HTMLElement[]} An array of view nodes.
+		 */
+		getNodes: function( callback, rendered ) {
+			var nodes = [];
 
 			this.getEditors( function( editor ) {
+				var self = this;
+
 				$( editor.getBody() )
-				.find( '[data-wpview-text="' + self.encodedText + '"]' )
-				.each( function ( i, node ) {
-					if ( callback ) {
-						callback( editor, node, $( node ).find( '.wpview-content' ).get( 0 ) );
-					}
+					.find( '[data-wpview-text="' + self.encodedText + '"]' )
+					.filter( function() {
+						var data;
 
-					nodes.push( node );
-				} );
+						if ( rendered == null ) {
+							return true;
+						}
+
+						data = $( this ).data( 'rendered' ) === true;
+
+						return rendered ? data : ! data;
+					} )
+					.each( function ( i, node ) {
+						nodes.push( node );
+						callback && callback.call( self, editor, node, $( node ).find( '.wpview-content' ).get( 0 ) );
+					} );
 			} );
 
 			return nodes;
 		},
-		setContent: function( html, option ) {
-			this.getNodes( function ( editor, node, content ) {
-				var el = ( option === 'wrap' || option === 'replace' ) ? node : content,
-					insert = html;
 
-				if ( _.isString( insert ) ) {
-					insert = editor.dom.createFragment( insert );
-				}
+		/**
+		 * Gets all the markers in each editor.
+		 *
+		 * @param {Function} A callback (optional).
+		 *
+		 * @return {HTMLElement[]} An array of marker nodes.
+		 */
+		getMarkers: function( callback ) {
+			var markers = [];
 
-				if ( option === 'replace' ) {
-					editor.dom.replace( insert, el );
-				} else {
-					el.innerHTML = '';
-					el.appendChild( insert );
+			this.getEditors( function( editor ) {
+				var self = this;
+
+				$( editor.getBody() )
+					.find( '[data-wpview-marker="' + this.encodedText + '"]' )
+					.each( function( i, node ) {
+						markers.push( node );
+						callback && callback.call( self, editor, node );
+					} );
+			} );
+
+			return markers;
+		},
+
+		/**
+		 * Replaces all markers with view nodes.
+		 */
+		replaceMarkers: function() {
+			this.getMarkers( function( editor, node ) {
+				if ( $( node ).text() !== decodeURIComponent( this.encodedText ) ) {
+					editor.dom.setAttrib( node, 'data-wpview-marker', null );
+					return;
 				}
+
+				editor.dom.replace(
+					editor.dom.createFragment(
+						'<div class="wpview-wrap" data-wpview-text="' + this.encodedText + '" data-wpview-type="' + this.type + '">' +
+							'<p class="wpview-selection-before">\u00a0</p>' +
+							'<div class="wpview-body" contenteditable="false">' +
+								'<div class="toolbar mce-arrow-down">' +
+									( _.isFunction( views[ this.type ].edit ) ? '<div class="dashicons dashicons-edit edit"></div>' : '' ) +
+									'<div class="dashicons dashicons-no remove"></div>' +
+								'</div>' +
+								'<div class="wpview-content wpview-type-' + this.type + '"></div>' +
+								( this.overlay ? '<div class="wpview-overlay"></div>' : '' ) +
+							'</div>' +
+							'<p class="wpview-selection-after">\u00a0</p>' +
+						'</div>'
+					),
+					node
+				);
+			} );
+		},
+
+		/**
+		 * Removes all markers.
+		 */
+		removeMarkers: function() {
+			this.getMarkers( function( editor, node ) {
+				editor.dom.setAttrib( node, 'data-wpview-marker', null );
 			} );
 		},
-		/* jshint scripturl: true */
-		setIframes: function ( head, body ) {
+
+		/**
+		 * Gets all the markers in each editor.
+		 *
+		 * @param {(String|HTMLElement)} The content to set.
+		 * @param {Function} A callback (optional).
+		 * @param {Boolean} Only set for (un)rendered nodes (optional).
+		 */
+		setContent: function( html, callback, rendered ) {
+			this.getNodes( function( editor, node, content ) {
+				content.innerHTML = '';
+				content.appendChild( _.isString( html ) ? editor.dom.createFragment( html ) : html );
+				callback && callback.apply( this, arguments );
+			}, rendered );
+		},
+
+		/**
+		 * Set an iframe as the view content.
+		 */
+		setIframes: function( head, body ) {
 			var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver,
 				importStyles = this.type === 'video' || this.type === 'audio' || this.type === 'playlist';
 
 			if ( head || body.indexOf( '<script' ) !== -1 ) {
-				this.getNodes( function ( editor, node, content ) {
+				this.getNodes( function( editor, node, content ) {
 					var dom = editor.dom,
 						styles = '',
 						bodyClasses = editor.getBody().className || '',
@@ -156,10 +487,11 @@
 						}
 					}
 
-					// Seems Firefox needs a bit of time to insert/set the view nodes, or the iframe will fail
-					// especially when switching Text => Visual.
+					// Seems Firefox needs a bit of time to insert/set the view nodes,
+					// or the iframe will fail especially when switching Text => Visual.
 					setTimeout( function() {
 						iframe = dom.add( content, 'iframe', {
+							/* jshint scripturl: true */
 							src: tinymce.Env.ie ? 'javascript:""' : '',
 							frameBorder: '0',
 							allowTransparency: 'true',
@@ -241,268 +573,58 @@
 								iframeDoc.body.className = editor.getBody().className;
 							});
 						}
-					}, waitInterval );
+					}, 50 );
 				});
 			} else {
 				this.setContent( body );
 			}
 		},
+
+		/**
+		 * Set a loader.
+		 */
+		setLoader: function() {
+			this.setContent(
+				'<div class="loading-placeholder">' +
+					'<div class="dashicons dashicons-admin-media"></div>' +
+					'<div class="wpview-loading"><ins></ins></div>' +
+				'</div>'
+			);
+		},
+
+		/**
+		 * Set an error.
+		 *
+		 * @param {String} The error message to set.
+		 * @param {String} A dashicon ID (optional). {@link https://developer.wordpress.org/resource/dashicons/}
+		 */
 		setError: function( message, dashicon ) {
 			this.setContent(
 				'<div class="wpview-error">' +
-					'<div class="dashicons dashicons-' + ( dashicon ? dashicon : 'no' ) + '"></div>' +
+					'<div class="dashicons dashicons-' + ( dashicon || 'no' ) + '"></div>' +
 					'<p>' + message + '</p>' +
 				'</div>'
 			);
-		},
-		rendered: function( value ) {
-			var notRendered;
-
-			this.getNodes( function( editor, node ) {
-				if ( value != null ) {
-					$( node ).data( 'rendered', value === true );
-				} else {
-					notRendered = notRendered || ! $( node ).data( 'rendered' );
-				}
-			} );
-
-			return ! notRendered;
 		}
 	} );
 
-	// take advantage of the Backbone extend method
+	// Take advantage of the Backbone extend method.
 	wp.mce.View.extend = Backbone.View.extend;
+} )( jQuery );
 
-	/**
-	 * wp.mce.views
-	 *
-	 * A set of utilities that simplifies adding custom UI within a TinyMCE editor.
-	 * At its core, it serves as a series of converters, transforming text to a
-	 * custom UI, and back again.
-	 */
-	wp.mce.views = {
-
-		/**
-		 * wp.mce.views.register( type, view )
-		 *
-		 * Registers a new TinyMCE view.
-		 *
-		 * @param type
-		 * @param constructor
-		 *
-		 */
-		register: function( type, constructor ) {
-			var defaultConstructor = {
-					type: type,
-					View: {},
-					toView: function( content ) {
-						var match = wp.shortcode.next( this.type, content );
-
-						if ( ! match ) {
-							return;
-						}
-
-						return {
-							index: match.index,
-							content: match.content,
-							options: {
-								shortcode: match.shortcode
-							}
-						};
-					}
-				};
-
-			constructor = _.defaults( constructor, defaultConstructor );
-			constructor.View = wp.mce.View.extend( constructor.View );
-
-			views[ type ] = constructor;
-		},
-
-		/**
-		 * wp.mce.views.get( id )
-		 *
-		 * Returns a TinyMCE view constructor.
-		 *
-		 * @param type
-		 */
-		get: function( type ) {
-			return views[ type ];
-		},
-
-		/**
-		 * wp.mce.views.unregister( type )
-		 *
-		 * Unregisters a TinyMCE view.
-		 *
-		 * @param type
-		 */
-		unregister: function( type ) {
-			delete views[ type ];
-		},
-
-		/**
-		 * wp.mce.views.unbind( editor )
-		 *
-		 * The editor DOM is being rebuilt, run cleanup.
-		 */
-		unbind: function() {
-			_.each( instances, function( instance ) {
-				instance.unbind();
-			} );
-		},
-
-		/**
-		 * toViews( content )
-		 * Scans a `content` string for each view's pattern, replacing any
-		 * matches with wrapper elements, and creates a new instance for
-		 * every match, which triggers the related data to be fetched.
-		 *
-		 * @param content
-		 */
-		toViews: function( content ) {
-			var pieces = [ { content: content } ],
-				current;
-
-			_.each( views, function( view, viewType ) {
-				current = pieces.slice();
-				pieces  = [];
-
-				_.each( current, function( piece ) {
-					var remaining = piece.content,
-						result;
-
-					// Ignore processed pieces, but retain their location.
-					if ( piece.processed ) {
-						pieces.push( piece );
-						return;
-					}
-
-					// Iterate through the string progressively matching views
-					// and slicing the string as we go.
-					while ( remaining && (result = view.toView( remaining )) ) {
-						// Any text before the match becomes an unprocessed piece.
-						if ( result.index ) {
-							pieces.push({ content: remaining.substring( 0, result.index ) });
-						}
-
-						// Add the processed piece for the match.
-						pieces.push({
-							content: wp.mce.views.toView( viewType, result.content, result.options ),
-							processed: true
-						});
-
-						// Update the remaining content.
-						remaining = remaining.slice( result.index + result.content.length );
-					}
-
-					// There are no additional matches. If any content remains,
-					// add it as an unprocessed piece.
-					if ( remaining ) {
-						pieces.push({ content: remaining });
-					}
-				});
-			});
-
-			return _.pluck( pieces, 'content' ).join('');
-		},
-
-		/**
-		 * Create a placeholder for a particular view type
-		 *
-		 * @param viewType
-		 * @param text
-		 * @param options
-		 *
-		 */
-		toView: function( viewType, text, options ) {
-			var view = wp.mce.views.get( viewType ),
-				encodedText = window.encodeURIComponent( text ),
-				instance, viewOptions;
-
-
-			if ( ! view ) {
-				return text;
-			}
-
-			if ( ! wp.mce.views.getInstance( encodedText ) ) {
-				viewOptions = options;
-				viewOptions.type = viewType;
-				viewOptions.encodedText = encodedText;
-				instance = new view.View( viewOptions );
-				instances[ encodedText ] = instance;
-			}
-
-			return wp.html.string({
-				tag: 'div',
-
-				attrs: {
-					'class': 'wpview-wrap',
-					'data-wpview-text': encodedText,
-					'data-wpview-type': viewType
-				},
-
-				content: '\u00a0'
-			});
-		},
-
-		/**
-		 * Refresh views after an update is made
-		 *
-		 * @param view {object} being refreshed
-		 * @param text {string} textual representation of the view
-		 * @param force {Boolean} whether to force rendering
-		 */
-		refreshView: function( view, text, force ) {
-			var encodedText = window.encodeURIComponent( text ),
-				viewOptions,
-				result, instance;
-
-			instance = wp.mce.views.getInstance( encodedText );
-
-			if ( ! instance ) {
-				result = view.toView( text );
-				viewOptions = result.options;
-				viewOptions.type = view.type;
-				viewOptions.encodedText = encodedText;
-				instance = new view.View( viewOptions );
-				instances[ encodedText ] = instance;
-			}
-
-			instance.render( force );
-		},
-
-		getInstance: function( encodedText ) {
-			return instances[ encodedText ];
-		},
-
-		/**
-		 * render( scope )
-		 *
-		 * Renders any view instances inside a DOM node `scope`.
-		 *
-		 * View instances are detected by the presence of wrapper elements.
-		 * To generate wrapper elements, pass your content through
-		 * `wp.mce.view.toViews( content )`.
-		 */
-		render: function( force ) {
-			_.each( instances, function( instance ) {
-				instance.render( force );
-			} );
-		},
-
-		edit: function( node ) {
-			var viewType = $( node ).data('wpview-type'),
-				view = wp.mce.views.get( viewType );
-
-			if ( view ) {
-				view.edit( node );
-			}
-		}
-	};
+/*
+ * The WordPress core TinyMCE views.
+ * Views for the gallery, audio, video, playlist and embed shortcodes,
+ * and a view for embeddable URLs.
+ */
+( function( $, views ) {
+	var mediaWindows = [],
+		windowIdx = 0,
+		waitInterval = 50;
 
-	wp.mce.views.register( 'gallery', {
+	views.register( 'gallery', {
 		View: {
-			template: media.template( 'editor-gallery' ),
+			template: wp.media.template( 'editor-gallery' ),
 
 			// The fallback post ID to use as a parent for galleries that don't
 			// specify the `ids` or `include` parameters.
@@ -520,7 +642,7 @@
 
 				this.attachments = wp.media.gallery.attachments( this.shortcode, this.postID );
 				this.dfd = this.attachments.more().done( function() {
-					self.render( true );
+					self.render();
 				} );
 			},
 
@@ -681,13 +803,8 @@
 					}
 				} )
 				.done( function( response ) {
-					if ( response ) {
-						self.parsed = response;
-						self.setIframes( response.head, response.body );
-						self.deferredListen();
-					} else {
-						self.fail( true );
-					}
+					self.parsed = response;
+					self.render();
 				} )
 				.fail( function( response ) {
 					self.fail( response || true );
@@ -703,18 +820,10 @@
 					}
 				}
 
-				if ( this.error.message ) {
-					if ( ( this.error.type === 'not-embeddable' && this.type === 'embed' ) || this.error.type === 'not-ssl' ||
-						this.error.type === 'no-items' ) {
-
-						this.setError( this.error.message, 'admin-media' );
-					} else {
-						this.setContent( '<p>' + this.original + '</p>', 'replace' );
-					}
-				} else if ( this.error.statusText ) {
-					this.setError( this.error.statusText, 'admin-media' );
-				} else if ( this.original ) {
-					this.setContent( '<p>' + this.original + '</p>', 'replace' );
+				if ( this.type === 'embedURL' ) {
+					this.removeMarkers();
+				} else {
+					this.setError( this.error.message || this.error.statusText, 'admin-media' );
 				}
 			},
 
@@ -742,6 +851,10 @@
 
 			unbind: function() {
 				this.stopPlayers( 'remove' );
+			},
+
+			getHtml: function() {
+				return this.parsed ? '\u00a0' : '';
 			}
 		},
 
@@ -790,7 +903,7 @@
 	 *
 	 * @mixes wp.mce.av
 	 */
-	wp.mce.views.register( 'video', _.extend( {}, wp.mce.av, {
+	views.register( 'video', _.extend( {}, wp.mce.av, {
 		state: 'video-details'
 	} ) );
 
@@ -799,7 +912,7 @@
 	 *
 	 * @mixes wp.mce.av
 	 */
-	wp.mce.views.register( 'audio', _.extend( {}, wp.mce.av, {
+	views.register( 'audio', _.extend( {}, wp.mce.av, {
 		state: 'audio-details'
 	} ) );
 
@@ -808,7 +921,7 @@
 	 *
 	 * @mixes wp.mce.av
 	 */
-	wp.mce.views.register( 'playlist', _.extend( {}, wp.mce.av, {
+	views.register( 'playlist', _.extend( {}, wp.mce.av, {
 		state: [ 'playlist-edit', 'video-playlist-edit' ]
 	} ) );
 
@@ -821,10 +934,10 @@
 			action: 'parse-embed',
 			initialize: function( options ) {
 				this.content = options.content;
-				this.original = options.url || options.shortcode.string();
 
 				if ( options.url ) {
-					this.shortcode = media.embed.shortcode( {
+					this.loader = false;
+					this.shortcode = wp.media.embed.shortcode( {
 						url: options.url
 					} );
 				} else {
@@ -838,7 +951,7 @@
 			}
 		} ),
 		edit: function( node ) {
-			var embed = media.embed,
+			var embed = wp.media.embed,
 				self = this,
 				frame,
 				data,
@@ -873,9 +986,9 @@
 		}
 	};
 
-	wp.mce.views.register( 'embed', _.extend( {}, wp.mce.embedMixin ) );
+	views.register( 'embed', _.extend( {}, wp.mce.embedMixin ) );
 
-	wp.mce.views.register( 'embedURL', _.extend( {}, wp.mce.embedMixin, {
+	views.register( 'embedURL', _.extend( {}, wp.mce.embedMixin, {
 		toView: function( content ) {
 			var re = /(^|<p>)(https?:\/\/[^\s"]+?)(<\/p>\s*|$)/gi,
 				match = re.exec( tinymce.trim( content ) );
@@ -893,5 +1006,4 @@
 			};
 		}
 	} ) );
-
-}(jQuery));
+} )( jQuery, wp.mce.views );
