Index: src/wp-admin/upload.php
===================================================================
--- src/wp-admin/upload.php	(revision 29037)
+++ src/wp-admin/upload.php	(working copy)
@@ -24,6 +24,7 @@
 	wp_enqueue_media();
 	wp_enqueue_script( 'media-grid' );
 	wp_enqueue_script( 'media' );
+	wp_localize_script( 'media-grid', 'mediaGridSettings', array( 'adminUrl' => parse_url( self_admin_url(), PHP_URL_PATH )  ) );
 
 	require_once( ABSPATH . 'wp-admin/admin-header.php' );
 	include( ABSPATH . 'wp-admin/admin-footer.php' );
Index: src/wp-includes/js/media-grid.js
===================================================================
--- src/wp-includes/js/media-grid.js	(revision 29037)
+++ src/wp-includes/js/media-grid.js	(working copy)
@@ -1,4 +1,4 @@
-/* global _wpMediaViewsL10n, setUserSetting, deleteUserSetting, MediaElementPlayer */
+/* global _wpMediaViewsL10n, setUserSetting, deleteUserSetting, MediaElementPlayer, mediaGridSettings*/
 (function($, _, Backbone, wp) {
 	var media = wp.media, l10n;
 
@@ -120,6 +120,7 @@
 		 * @global wp.Uploader
 		 */
 		initialize: function() {
+			var self = this;
 			_.defaults( this.options, {
 				title:     l10n.mediaLibraryTitle,
 				modal:     false,
@@ -168,6 +169,24 @@
 			this.createStates();
 			this.bindHandlers();
 			this.render();
+
+			// Set up the Backbone router after a brief delay
+			_.delay( function(){
+				wp.media.mediarouter = new media.view.Frame.Router( self );
+				// Verify pushState support and activate
+				if ( window.history && window.history.pushState ) {
+					Backbone.history.start({
+						root: mediaGridSettings.adminUrl,
+						pushState: true
+					});
+				}
+			}, 150);
+
+			// Update the URL when entering search string (at most once per second)
+			$( '#media-search-input' ).on( 'input', _.debounce( function() {
+				var $val = $( this ).val();
+				wp.media.mediarouter.navigate( wp.media.mediarouter.baseUrl( ( '' == $val ) ? '' : ( '?search=' + $val ) ) );
+			}, 1000 ) );
 		},
 
 		createSelection: function() {
@@ -213,46 +232,21 @@
 
 			// Handle a frame-level event for editing an attachment.
 			this.on( 'edit:attachment', this.editAttachment, this );
-			this.on( 'edit:attachment:next', this.editNextAttachment, this );
-			this.on( 'edit:attachment:previous', this.editPreviousAttachment, this );
 		},
 
-		editPreviousAttachment: function( currentModel ) {
-			var library = this.state().get('library'),
-				currentModelIndex = library.indexOf( currentModel );
-			this.trigger( 'edit:attachment', library.at( currentModelIndex - 1 ) );
-		},
-
-		editNextAttachment: function( currentModel ) {
-			var library = this.state().get('library'),
-				currentModelIndex = library.indexOf( currentModel );
-			this.trigger( 'edit:attachment', library.at( currentModelIndex + 1 ) );
-		},
-
 		/**
 		 * Open the Edit Attachment modal.
 		 */
 		editAttachment: function( model ) {
-			var library = this.state().get('library'), hasPrevious, hasNext;
-			if ( library.indexOf( model ) > 0 ) {
-				hasPrevious = true;
-			}
-			else {
-				hasPrevious = false;
-			}
-			if ( library.indexOf( model ) < library.length - 1 ) {
-				hasNext = true;
-			}
-			else {
-				hasNext = false;
-			}
-
-			new media.view.Frame.EditAttachment({
-				hasPrevious:    hasPrevious,
-				hasNext:        hasNext,
-				model:          model,
-				gridController: this
+			var self = this;
+			// Create a new EditAttachment frame, passing along the library.
+			this.editAttachmentFrame = new media.view.Frame.EditAttachments({
+				library: this.state().get('library'),
+				model: model
 			});
+			$( 'body' ).on( 'keydown.media-modal', function( e ) {
+				self.editAttachmentFrame.keyEvent( e );
+				} );
 		},
 
 		/**
@@ -324,6 +318,59 @@
 	});
 
 	/**
+	 * A router for handling the browser history and application state
+	 */
+	media.view.Frame.Router = Backbone.Router.extend({
+
+		mediaFrame: '',
+
+		initialize: function( mediaFrame ){
+			this.mediaFrame = mediaFrame;
+		},
+
+		routes: {
+			'upload.php?item=:slug':    'showitem',
+			'upload.php?search=:query': 'search',
+			':default':                 'defaultRoute'
+		},
+
+		// Map routes against the page URL
+		baseUrl: function( url ) {
+			return 'upload.php' + url;
+		},
+
+		// Respond to the search route by filling the search field and trigggering the input event
+		search: function( query ) {
+			// Ensure modal closed, see back button
+			this.closeModal();
+			$( '#media-search-input' ).val( query ).trigger( 'input' );
+		},
+
+		// Show the modal with a specific item
+		showitem: function( query ) {
+			var library = this.mediaFrame.state().get('library');
+
+			// Remove existing modal if present
+			this.closeModal();
+			// Trigger the media frame to open the correct item
+			this.mediaFrame.trigger( 'edit:attachment', library.findWhere( { id: parseInt( query, 10 ) } ) );
+		},
+
+		// Close the modal if set up
+		closeModal: function() {
+			if ( 'undefined' !== typeof this.mediaFrame.editAttachmentFrame ) {
+				this.mediaFrame.editAttachmentFrame.modal.close();
+			}
+		},
+
+		// Default route: make sure the modal and search are reset
+		defaultRoute: function() {
+			this.closeModal();
+			$( '#media-search-input' ).val( '' ).trigger( 'input' );
+		}
+	});
+
+	/**
 	 * A frame for editing the details of a specific media item.
 	 *
 	 * Opens in a modal by default.
@@ -330,7 +377,7 @@
 	 *
 	 * Requires an attachment model to be passed in the options hash under `model`.
 	 */
-	media.view.Frame.EditAttachment = media.view.Frame.extend({
+	media.view.Frame.EditAttachments = media.view.Frame.extend({
 
 		className: 'edit-attachment-frame',
 		template: media.template( 'edit-attachment-frame' ),
@@ -352,6 +399,13 @@
 				state: 'edit-attachment'
 			});
 
+			this.library = this.options.library;
+			if ( this.options.model ) {
+				this.model = this.options.model;
+			} else {
+				this.model = this.library.at( 0 );
+			}
+
 			this.createStates();
 
 			this.on( 'content:render:edit-metadata', this.editMetadataContent, this );
@@ -358,7 +412,7 @@
 			this.on( 'content:render:edit-image', this.editImageContentUgh, this );
 
 			// Only need a tab to Edit Image for images.
-			if ( this.model.get( 'type' ) === 'image' ) {
+			if ( 'undefined' !== typeof this.model && this.model.get( 'type' ) === 'image' ) {
 				this.on( 'router:create', this.createRouter, this );
 				this.on( 'router:render', this.browseRouter, this );
 			}
@@ -373,6 +427,9 @@
 				// Completely destroy the modal DOM element when closing it.
 				this.modal.close = function() {
 					self.modal.remove();
+					$( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */
+					// Reset the browser URL
+					self.resetRoute();
 				};
 
 				this.modal.content( this );
@@ -412,6 +469,8 @@
 				model:      this.model
 			});
 			this.content.set( view );
+			// Update browser url when navigating media details
+			wp.media.mediarouter.navigate( wp.media.mediarouter.baseUrl( '?item=' + this.model.id ) );
 		},
 
 		/**
@@ -464,14 +523,27 @@
 			});
 		},
 
+		getCurrentIndex: function() {
+			return this.library.indexOf( this.model );
+		},
+
+		hasNext: function() {
+			return ( this.getCurrentIndex() + 1 ) < this.library.length;
+		},
+
+		hasPrevious: function() {
+			return ( this.getCurrentIndex() - 1 ) > -1;
+		},
+
 		/**
 		 * Click handler to switch to the previous media item.
 		 */
 		previousMediaItem: function() {
-			if ( ! this.options.hasPrevious )
+			if ( ! this.hasPrevious() ) {
 				return;
-			this.modal.close();
-			this.options.gridController.trigger( 'edit:attachment:previous', this.model );
+			}
+			this.model = this.library.at( this.getCurrentIndex() - 1 );
+			this.editMetadataContent();
 		},
 
 		/**
@@ -478,12 +550,46 @@
 		 * Click handler to switch to the next media item.
 		 */
 		nextMediaItem: function() {
-			if ( ! this.options.hasNext )
+			if ( ! this.hasNext() ) {
 				return;
-			this.modal.close();
-			this.options.gridController.trigger( 'edit:attachment:next', this.model );
+			}
+			this.model = this.library.at( this.getCurrentIndex() + 1 );
+			this.editMetadataContent();
+		},
+		/**
+		 * Respond to the keyboard events: right arrow, left arrow, escape.
+		 */
+		keyEvent: function( event ) {
+			var $target = $( event.target );
+
+			// Pressing the escape key routes back to main url
+			if ( event.keyCode === 27 ) {
+				this.resetRoute();
+				return event;
+			}
+
+			//Don't go left/right if we are in a textarea or input field
+			if ( $target.is( 'input' ) || $target.is( 'textarea' ) ) {
+				return event;
+			}
+
+			// The right arrow key
+			if ( event.keyCode === 39 ) {
+				if ( ! this.hasNext ) { return; }
+				_.debounce( this.nextMediaItem(), 250 );
+			}
+
+			// The left arrow key
+			if ( event.keyCode === 37 ) {
+				if ( ! this.hasPrevious ) { return; }
+				_.debounce( this.previousMediaItem(), 250 );
+			}
+		},
+
+		resetRoute: function() {
+			wp.media.mediarouter.navigate( wp.media.mediarouter.baseUrl( '' ) );
+			return;
 		}
-
 	});
 
 	media.view.GridFieldOptions = media.View.extend({
