text: /*!
   * OOjs UI v0.23.0
   * https://www.mediawiki.org/wiki/OOjs_UI
   *
   * Copyright 2011–2017 OOjs UI Team and other contributors.
   * Released under the MIT license
   * http://oojs.mit-license.org
   *
   * Date: 2017-09-05T21:23:58Z
   */
  ( function ( OO ) {
  
  'use strict';
  
  /**
   * An ActionWidget is a {@link OO.ui.ButtonWidget button widget} that executes an action.
   * Action widgets are used with OO.ui.ActionSet, which manages the behavior and availability
   * of the actions.
   *
   * Both actions and action sets are primarily used with {@link OO.ui.Dialog Dialogs}.
   * Please see the [OOjs UI documentation on MediaWiki] [1] for more information
   * and examples.
   *
   * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Action_sets
   *
   * @class
   * @extends OO.ui.ButtonWidget
   * @mixins OO.ui.mixin.PendingElement
   *
   * @constructor
   * @param {Object} [config] Configuration options
   * @cfg {string} [action] Symbolic name of the action (e.g., ‘continue’ or ‘cancel’).
   * @cfg {string[]} [modes] Symbolic names of the modes (e.g., ‘edit’ or ‘read’) in which the action
   *  should be made available. See the action set's {@link OO.ui.ActionSet#setMode setMode} method
   *  for more information about setting modes.
   * @cfg {boolean} [framed=false] Render the action button with a frame
   */
  OO.ui.ActionWidget = function OoUiActionWidget( config ) {
  	// Configuration initialization
  	config = $.extend( { framed: false }, config );
  
  	// Parent constructor
  	OO.ui.ActionWidget.parent.call( this, config );
  
  	// Mixin constructors
  	OO.ui.mixin.PendingElement.call( this, config );
  
  	// Properties
  	this.action = config.action || '';
  	this.modes = config.modes || [];
  	this.width = 0;
  	this.height = 0;
  
  	// Initialization
  	this.$element.addClass( 'oo-ui-actionWidget' );
  };
  
  /* Setup */
  
  OO.inheritClass( OO.ui.ActionWidget, OO.ui.ButtonWidget );
  OO.mixinClass( OO.ui.ActionWidget, OO.ui.mixin.PendingElement );
  
  /* Methods */
  
  /**
   * Check if the action is configured to be available in the specified `mode`.
   *
   * @param {string} mode Name of mode
   * @return {boolean} The action is configured with the mode
   */
  OO.ui.ActionWidget.prototype.hasMode = function ( mode ) {
  	return this.modes.indexOf( mode ) !== -1;
  };
  
  /**
   * Get the symbolic name of the action (e.g., ‘continue’ or ‘cancel’).
   *
   * @return {string}
   */
  OO.ui.ActionWidget.prototype.getAction = function () {
  	return this.action;
  };
  
  /**
   * Get the symbolic name of the mode or modes for which the action is configured to be available.
   *
   * The current mode is set with the action set's {@link OO.ui.ActionSet#setMode setMode} method.
   * Only actions that are configured to be avaiable in the current mode will be visible. All other actions
   * are hidden.
   *
   * @return {string[]}
   */
  OO.ui.ActionWidget.prototype.getModes = function () {
  	return this.modes.slice();
  };
  
  /* eslint-disable no-unused-vars */
  /**
   * ActionSets manage the behavior of the {@link OO.ui.ActionWidget action widgets} that comprise them.
   * Actions can be made available for specific contexts (modes) and circumstances
   * (abilities). Action sets are primarily used with {@link OO.ui.Dialog Dialogs}.
   *
   * ActionSets contain two types of actions:
   *
   * - Special: Special actions are the first visible actions with special flags, such as 'safe' and 'primary', the default special flags. Additional special flags can be configured in subclasses with the static #specialFlags property.
   * - Other: Other actions include all non-special visible actions.
   *
   * Please see the [OOjs UI documentation on MediaWiki][1] for more information.
   *
   *     @example
   *     // Example: An action set used in a process dialog
   *     function MyProcessDialog( config ) {
   *         MyProcessDialog.parent.call( this, config );
   *     }
   *     OO.inheritClass( MyProcessDialog, OO.ui.ProcessDialog );
   *     MyProcessDialog.static.title = 'An action set in a process dialog';
   *     MyProcessDialog.static.name = 'myProcessDialog';
   *     // An action set that uses modes ('edit' and 'help' mode, in this example).
   *     MyProcessDialog.static.actions = [
   *         { action: 'continue', modes: 'edit', label: 'Continue', flags: [ 'primary', 'constructive' ] },
   *         { action: 'help', modes: 'edit', label: 'Help' },
   *         { modes: 'edit', label: 'Cancel', flags: 'safe' },
   *         { action: 'back', modes: 'help', label: 'Back', flags: 'safe' }
   *     ];
   *
   *     MyProcessDialog.prototype.initialize = function () {
   *         MyProcessDialog.parent.prototype.initialize.apply( this, arguments );
   *         this.panel1 = new OO.ui.PanelLayout( { padded: true, expanded: false } );
   *         this.panel1.$element.append( '<p>This dialog uses an action set (continue, help, cancel, back) configured with modes. This is edit mode. Click \'help\' to see help mode.</p>' );
   *         this.panel2 = new OO.ui.PanelLayout( { padded: true, expanded: false } );
   *         this.panel2.$element.append( '<p>This is help mode. Only the \'back\' action widget is configured to be visible here. Click \'back\' to return to \'edit\' mode.</p>' );
   *         this.stackLayout = new OO.ui.StackLayout( {
   *             items: [ this.panel1, this.panel2 ]
   *         } );
   *         this.$body.append( this.stackLayout.$element );
   *     };
   *     MyProcessDialog.prototype.getSetupProcess = function ( data ) {
   *         return MyProcessDialog.parent.prototype.getSetupProcess.call( this, data )
   *             .next( function () {
   *                 this.actions.setMode( 'edit' );
   *             }, this );
   *     };
   *     MyProcessDialog.prototype.getActionProcess = function ( action ) {
   *         if ( action === 'help' ) {
   *             this.actions.setMode( 'help' );
   *             this.stackLayout.setItem( this.panel2 );
   *         } else if ( action === 'back' ) {
   *             this.actions.setMode( 'edit' );
   *             this.stackLayout.setItem( this.panel1 );
   *         } else if ( action === 'continue' ) {
   *             var dialog = this;
   *             return new OO.ui.Process( function () {
   *                 dialog.close();
   *             } );
   *         }
   *         return MyProcessDialog.parent.prototype.getActionProcess.call( this, action );
   *     };
   *     MyProcessDialog.prototype.getBodyHeight = function () {
   *         return this.panel1.$element.outerHeight( true );
   *     };
   *     var windowManager = new OO.ui.WindowManager();
   *     $( 'body' ).append( windowManager.$element );
   *     var dialog = new MyProcessDialog( {
   *         size: 'medium'
   *     } );
   *     windowManager.addWindows( [ dialog ] );
   *     windowManager.openWindow( dialog );
   *
   * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Action_sets
   *
   * @abstract
   * @class
   * @mixins OO.EventEmitter
   *
   * @constructor
   * @param {Object} [config] Configuration options
   */
  OO.ui.ActionSet = function OoUiActionSet( config ) {
  	// Configuration initialization
  	config = config || {};
  
  	// Mixin constructors
  	OO.EventEmitter.call( this );
  
  	// Properties
  	this.list = [];
  	this.categories = {
  		actions: 'getAction',
  		flags: 'getFlags',
  		modes: 'getModes'
  	};
  	this.categorized = {};
  	this.special = {};
  	this.others = [];
  	this.organized = false;
  	this.changing = false;
  	this.changed = false;
  };
  /* eslint-enable no-unused-vars */
  
  /* Setup */
  
  OO.mixinClass( OO.ui.ActionSet, OO.EventEmitter );
  
  /* Static Properties */
  
  /**
   * Symbolic name of the flags used to identify special actions. Special actions are displayed in the
   *  header of a {@link OO.ui.ProcessDialog process dialog}.
   *  See the [OOjs UI documentation on MediaWiki][2] for more information and examples.
   *
   *  [2]:https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs
   *
   * @abstract
   * @static
   * @inheritable
   * @property {string}
   */
  OO.ui.ActionSet.static.specialFlags = [ 'safe', 'primary' ];
  
  /* Events */
  
  /**
   * @event click
   *
   * A 'click' event is emitted when an action is clicked.
   *
   * @param {OO.ui.ActionWidget} action Action that was clicked
   */
  
  /**
   * @event add
   *
   * An 'add' event is emitted when actions are {@link #method-add added} to the action set.
   *
   * @param {OO.ui.ActionWidget[]} added Actions added
   */
  
  /**
   * @event remove
   *
   * A 'remove' event is emitted when actions are {@link #method-remove removed}
   *  or {@link #clear cleared}.
   *
   * @param {OO.ui.ActionWidget[]} added Actions removed
   */
  
  /**
   * @event change
   *
   * A 'change' event is emitted when actions are {@link #method-add added}, {@link #clear cleared},
   * or {@link #method-remove removed} from the action set or when the {@link #setMode mode} is changed.
   *
   */
  
  /* Methods */
  
  /**
   * Handle action change events.
   *
   * @private
   * @fires change
   */
  OO.ui.ActionSet.prototype.onActionChange = function () {
  	this.organized = false;
  	if ( this.changing ) {
  		this.changed = true;
  	} else {
  		this.emit( 'change' );
  	}
  };
  
  /**
   * Check if an action is one of the special actions.
   *
   * @param {OO.ui.ActionWidget} action Action to check
   * @return {boolean} Action is special
   */
  OO.ui.ActionSet.prototype.isSpecial = function ( action ) {
  	var flag;
  
  	for ( flag in this.special ) {
  		if ( action === this.special[ flag ] ) {
  			return true;
  		}
  	}
  
  	return false;
  };
  
  /**
   * Get action widgets based on the specified filter: ‘actions’, ‘flags’, ‘modes’, ‘visible’,
   *  or ‘disabled’.
   *
   * @param {Object} [filters] Filters to use, omit to get all actions
   * @param {string|string[]} [filters.actions] Actions that action widgets must have
   * @param {string|string[]} [filters.flags] Flags that action widgets must have (e.g., 'safe')
   * @param {string|string[]} [filters.modes] Modes that action widgets must have
   * @param {boolean} [filters.visible] Action widgets must be visible
   * @param {boolean} [filters.disabled] Action widgets must be disabled
   * @return {OO.ui.ActionWidget[]} Action widgets matching all criteria
   */
  OO.ui.ActionSet.prototype.get = function ( filters ) {
  	var i, len, list, category, actions, index, match, matches;
  
  	if ( filters ) {
  		this.organize();
  
  		// Collect category candidates
  		matches = [];
  		for ( category in this.categorized ) {
  			list = filters[ category ];
  			if ( list ) {
  				if ( !Array.isArray( list ) ) {
  					list = [ list ];
  				}
  				for ( i = 0, len = list.length; i < len; i++ ) {
  					actions = this.categorized[ category ][ list[ i ] ];
  					if ( Array.isArray( actions ) ) {
  						matches.push.apply( matches, actions );
  					}
  				}
  			}
  		}
  		// Remove by boolean filters
  		for ( i = 0, len = matches.length; i < len; i++ ) {
  			match = matches[ i ];
  			if (
  				( filters.visible !== undefined && match.isVisible() !== filters.visible ) ||
  				( filters.disabled !== undefined && match.isDisabled() !== filters.disabled )
  			) {
  				matches.splice( i, 1 );
  				len--;
  				i--;
  			}
  		}
  		// Remove duplicates
  		for ( i = 0, len = matches.length; i < len; i++ ) {
  			match = matches[ i ];
  			index = matches.lastIndexOf( match );
  			while ( index !== i ) {
  				matches.splice( index, 1 );
  				len--;
  				index = matches.lastIndexOf( match );
  			}
  		}
  		return matches;
  	}
  	return this.list.slice();
  };
  
  /**
   * Get 'special' actions.
   *
   * Special actions are the first visible action widgets with special flags, such as 'safe' and 'primary'.
   * Special flags can be configured in subclasses by changing the static #specialFlags property.
   *
   * @return {OO.ui.ActionWidget[]|null} 'Special' action widgets.
   */
  OO.ui.ActionSet.prototype.getSpecial = function () {
  	this.organize();
  	return $.extend( {}, this.special );
  };
  
  /**
   * Get 'other' actions.
   *
   * Other actions include all non-special visible action widgets.
   *
   * @return {OO.ui.ActionWidget[]} 'Other' action widgets
   */
  OO.ui.ActionSet.prototype.getOthers = function () {
  	this.organize();
  	return this.others.slice();
  };
  
  /**
   * Set the mode  (e.g., ‘edit’ or ‘view’). Only {@link OO.ui.ActionWidget#modes actions} configured
   * to be available in the specified mode will be made visible. All other actions will be hidden.
   *
   * @param {string} mode The mode. Only actions configured to be available in the specified
   *  mode will be made visible.
   * @chainable
   * @fires toggle
   * @fires change
   */
  OO.ui.ActionSet.prototype.setMode = function ( mode ) {
  	var i, len, action;
  
  	this.changing = true;
  	for ( i = 0, len = this.list.length; i < len; i++ ) {
  		action = this.list[ i ];
  		action.toggle( action.hasMode( mode ) );
  	}
  
  	this.organized = false;
  	this.changing = false;
  	this.emit( 'change' );
  
  	return this;
  };
  
  /**
   * Set the abilities of the specified actions.
   *
   * Action widgets that are configured with the specified actions will be enabled
   * or disabled based on the boolean values specified in the `actions`
   * parameter.
   *
   * @param {Object.<string,boolean>} actions A list keyed by action name with boolean
   *  values that indicate whether or not the action should be enabled.
   * @chainable
   */
  OO.ui.ActionSet.prototype.setAbilities = function ( actions ) {
  	var i, len, action, item;
  
  	for ( i = 0, len = this.list.length; i < len; i++ ) {
  		item = this.list[ i ];
  		action = item.getAction();
  		if ( actions[ action ] !== undefined ) {
  			item.setDisabled( !actions[ action ] );
  		}
  	}
  
  	return this;
  };
  
  /**
   * Executes a function once per action.
   *
   * When making changes to multiple actions, use this method instead of iterating over the actions
   * manually to defer emitting a #change event until after all actions have been changed.
   *
   * @param {Object|null} filter Filters to use to determine which actions to iterate over; see #get
   * @param {Function} callback Callback to run for each action; callback is invoked with three
   *   arguments: the action, the action's index, the list of actions being iterated over
   * @chainable
   */
  OO.ui.ActionSet.prototype.forEach = function ( filter, callback ) {
  	this.changed = false;
  	this.changing = true;
  	this.get( filter ).forEach( callback );
  	this.changing = false;
  	if ( this.changed ) {
  		this.emit( 'change' );
  	}
  
  	return this;
  };
  
  /**
   * Add action widgets to the action set.
   *
   * @param {OO.ui.ActionWidget[]} actions Action widgets to add
   * @chainable
   * @fires add
   * @fires change
   */
  OO.ui.ActionSet.prototype.add = function ( actions ) {
  	var i, len, action;
  
  	this.changing = true;
  	for ( i = 0, len = actions.length; i < len; i++ ) {
  		action = actions[ i ];
  		action.connect( this, {
  			click: [ 'emit', 'click', action ],
  			toggle: [ 'onActionChange' ]
  		} );
  		this.list.push( action );
  	}
  	this.organized = false;
  	this.emit( 'add', actions );
  	this.changing = false;
  	this.emit( 'change' );
  
  	return this;
  };
  
  /**
   * Remove action widgets from the set.
   *
   * To remove all actions, you may wish to use the #clear method instead.
   *
   * @param {OO.ui.ActionWidget[]} actions Action widgets to remove
   * @chainable
   * @fires remove
   * @fires change
   */
  OO.ui.ActionSet.prototype.remove = function ( actions ) {
  	var i, len, index, action;
  
  	this.changing = true;
  	for ( i = 0, len = actions.length; i < len; i++ ) {
  		action = actions[ i ];
  		index = this.list.indexOf( action );
  		if ( index !== -1 ) {
  			action.disconnect( this );
  			this.list.splice( index, 1 );
  		}
  	}
  	this.organized = false;
  	this.emit( 'remove', actions );
  	this.changing = false;
  	this.emit( 'change' );
  
  	return this;
  };
  
  /**
   * Remove all action widets from the set.
   *
   * To remove only specified actions, use the {@link #method-remove remove} method instead.
   *
   * @chainable
   * @fires remove
   * @fires change
   */
  OO.ui.ActionSet.prototype.clear = function () {
  	var i, len, action,
  		removed = this.list.slice();
  
  	this.changing = true;
  	for ( i = 0, len = this.list.length; i < len; i++ ) {
  		action = this.list[ i ];
  		action.disconnect( this );
  	}
  
  	this.list = [];
  
  	this.organized = false;
  	this.emit( 'remove', removed );
  	this.changing = false;
  	this.emit( 'change' );
  
  	return this;
  };
  
  /**
   * Organize actions.
   *
   * This is called whenever organized information is requested. It will only reorganize the actions
   * if something has changed since the last time it ran.
   *
   * @private
   * @chainable
   */
  OO.ui.ActionSet.prototype.organize = function () {
  	var i, iLen, j, jLen, flag, action, category, list, item, special,
  		specialFlags = this.constructor.static.specialFlags;
  
  	if ( !this.organized ) {
  		this.categorized = {};
  		this.special = {};
  		this.others = [];
  		for ( i = 0, iLen = this.list.length; i < iLen; i++ ) {
  			action = this.list[ i ];
  			if ( action.isVisible() ) {
  				// Populate categories
  				for ( category in this.categories ) {
  					if ( !this.categorized[ category ] ) {
  						this.categorized[ category ] = {};
  					}
  					list = action[ this.categories[ category ] ]();
  					if ( !Array.isArray( list ) ) {
  						list = [ list ];
  					}
  					for ( j = 0, jLen = list.length; j < jLen; j++ ) {
  						item = list[ j ];
  						if ( !this.categorized[ category ][ item ] ) {
  							this.categorized[ category ][ item ] = [];
  						}
  						this.categorized[ category ][ item ].push( action );
  					}
  				}
  				// Populate special/others
  				special = false;
  				for ( j = 0, jLen = specialFlags.length; j < jLen; j++ ) {
  					flag = specialFlags[ j ];
  					if ( !this.special[ flag ] && action.hasFlag( flag ) ) {
  						this.special[ flag ] = action;
  						special = true;
  						break;
  					}
  				}
  				if ( !special ) {
  					this.others.push( action );
  				}
  			}
  		}
  		this.organized = true;
  	}
  
  	return this;
  };
  
  /**
   * Errors contain a required message (either a string or jQuery selection) that is used to describe what went wrong
   * in a {@link OO.ui.Process process}. The error's #recoverable and #warning configurations are used to customize the
   * appearance and functionality of the error interface.
   *
   * The basic error interface contains a formatted error message as well as two buttons: 'Dismiss' and 'Try again' (i.e., the error
   * is 'recoverable' by default). If the error is not recoverable, the 'Try again' button will not be rendered and the widget
   * that initiated the failed process will be disabled.
   *
   * If the error is a warning, the error interface will include a 'Dismiss' and a 'Continue' button, which will try the
   * process again.
   *
   * For an example of error interfaces, please see the [OOjs UI documentation on MediaWiki][1].
   *
   * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Processes_and_errors
   *
   * @class
   *
   * @constructor
   * @param {string|jQuery} message Description of error
   * @param {Object} [config] Configuration options
   * @cfg {boolean} [recoverable=true] Error is recoverable.
   *  By default, errors are recoverable, and users can try the process again.
   * @cfg {boolean} [warning=false] Error is a warning.
   *  If the error is a warning, the error interface will include a
   *  'Dismiss' and a 'Continue' button. It is the responsibility of the developer to ensure that the warning
   *  is not triggered a second time if the user chooses to continue.
   */
  OO.ui.Error = function OoUiError( message, config ) {
  	// Allow passing positional parameters inside the config object
  	if ( OO.isPlainObject( message ) && config === undefined ) {
  		config = message;
  		message = config.message;
  	}
  
  	// Configuration initialization
  	config = config || {};
  
  	// Properties
  	this.message = message instanceof jQuery ? message : String( message );
  	this.recoverable = config.recoverable === undefined || !!config.recoverable;
  	this.warning = !!config.warning;
  };
  
  /* Setup */
  
  OO.initClass( OO.ui.Error );
  
  /* Methods */
  
  /**
   * Check if the error is recoverable.
   *
   * If the error is recoverable, users are able to try the process again.
   *
   * @return {boolean} Error is recoverable
   */
  OO.ui.Error.prototype.isRecoverable = function () {
  	return this.recoverable;
  };
  
  /**
   * Check if the error is a warning.
   *
   * If the error is a warning, the error interface will include a 'Dismiss' and a 'Continue' button.
   *
   * @return {boolean} Error is warning
   */
  OO.ui.Error.prototype.isWarning = function () {
  	return this.warning;
  };
  
  /**
   * Get error message as DOM nodes.
   *
   * @return {jQuery} Error message in DOM nodes
   */
  OO.ui.Error.prototype.getMessage = function () {
  	return this.message instanceof jQuery ?
  		this.message.clone() :
  		$( '<div>' ).text( this.message ).contents();
  };
  
  /**
   * Get the error message text.
   *
   * @return {string} Error message
   */
  OO.ui.Error.prototype.getMessageText = function () {
  	return this.message instanceof jQuery ? this.message.text() : this.message;
  };
  
  /**
   * A Process is a list of steps that are called in sequence. The step can be a number, a jQuery promise,
   * or a function:
   *
   * - **number**: the process will wait for the specified number of milliseconds before proceeding.
   * - **promise**: the process will continue to the next step when the promise is successfully resolved
   *  or stop if the promise is rejected.
   * - **function**: the process will execute the function. The process will stop if the function returns
   *  either a boolean `false` or a promise that is rejected; if the function returns a number, the process
   *  will wait for that number of milliseconds before proceeding.
   *
   * If the process fails, an {@link OO.ui.Error error} is generated. Depending on how the error is
   * configured, users can dismiss the error and try the process again, or not. If a process is stopped,
   * its remaining steps will not be performed.
   *
   * @class
   *
   * @constructor
   * @param {number|jQuery.Promise|Function} step Number of miliseconds to wait before proceeding, promise
   *  that must be resolved before proceeding, or a function to execute. See #createStep for more information. see #createStep for more information
   * @param {Object} [context=null] Execution context of the function. The context is ignored if the step is
   *  a number or promise.
   */
  OO.ui.Process = function ( step, context ) {
  	// Properties
  	this.steps = [];
  
  	// Initialization
  	if ( step !== undefined ) {
  		this.next( step, context );
  	}
  };
  
  /* Setup */
  
  OO.initClass( OO.ui.Process );
  
  /* Methods */
  
  /**
   * Start the process.
   *
   * @return {jQuery.Promise} Promise that is resolved when all steps have successfully completed.
   *  If any of the steps return a promise that is rejected or a boolean false, this promise is rejected
   *  and any remaining steps are not performed.
   */
  OO.ui.Process.prototype.execute = function () {
  	var i, len, promise;
  
  	/**
  	 * Continue execution.
  	 *
  	 * @ignore
  	 * @param {Array} step A function and the context it should be called in
  	 * @return {Function} Function that continues the process
  	 */
  	function proceed( step ) {
  		return function () {
  			// Execute step in the correct context
  			var deferred,
  				result = step.callback.call( step.context );
  
  			if ( result === false ) {
  				// Use rejected promise for boolean false results
  				return $.Deferred().reject( [] ).promise();
  			}
  			if ( typeof result === 'number' ) {
  				if ( result < 0 ) {
  					throw new Error( 'Cannot go back in time: flux capacitor is out of service' );
  				}
  				// Use a delayed promise for numbers, expecting them to be in milliseconds
  				deferred = $.Deferred();
  				setTimeout( deferred.resolve, result );
  				return deferred.promise();
  			}
  			if ( result instanceof OO.ui.Error ) {
  				// Use rejected promise for error
  				return $.Deferred().reject( [ result ] ).promise();
  			}
  			if ( Array.isArray( result ) && result.length && result[ 0 ] instanceof OO.ui.Error ) {
  				// Use rejected promise for list of errors
  				return $.Deferred().reject( result ).promise();
  			}
  			// Duck-type the object to see if it can produce a promise
  			if ( result && $.isFunction( result.promise ) ) {
  				// Use a promise generated from the result
  				return result.promise();
  			}
  			// Use resolved promise for other results
  			return $.Deferred().resolve().promise();
  		};
  	}
  
  	if ( this.steps.length ) {
  		// Generate a chain reaction of promises
  		promise = proceed( this.steps[ 0 ] )();
  		for ( i = 1, len = this.steps.length; i < len; i++ ) {
  			promise = promise.then( proceed( this.steps[ i ] ) );
  		}
  	} else {
  		promise = $.Deferred().resolve().promise();
  	}
  
  	return promise;
  };
  
  /**
   * Create a process step.
   *
   * @private
   * @param {number|jQuery.Promise|Function} step
   *
   * - Number of milliseconds to wait before proceeding
   * - Promise that must be resolved before proceeding
   * - Function to execute
   *   - If the function returns a boolean false the process will stop
   *   - If the function returns a promise, the process will continue to the next
   *     step when the promise is resolved or stop if the promise is rejected
   *   - If the function returns a number, the process will wait for that number of
   *     milliseconds before proceeding
   * @param {Object} [context=null] Execution context of the function. The context is
   *  ignored if the step is a number or promise.
   * @return {Object} Step object, with `callback` and `context` properties
   */
  OO.ui.Process.prototype.createStep = function ( step, context ) {
  	if ( typeof step === 'number' || $.isFunction( step.promise ) ) {
  		return {
  			callback: function () {
  				return step;
  			},
  			context: null
  		};
  	}
  	if ( $.isFunction( step ) ) {
  		return {
  			callback: step,
  			context: context
  		};
  	}
  	throw new Error( 'Cannot create process step: number, promise or function expected' );
  };
  
  /**
   * Add step to the beginning of the process.
   *
   * @inheritdoc #createStep
   * @return {OO.ui.Process} this
   * @chainable
   */
  OO.ui.Process.prototype.first = function ( step, context ) {
  	this.steps.unshift( this.createStep( step, context ) );
  	return this;
  };
  
  /**
   * Add step to the end of the process.
   *
   * @inheritdoc #createStep
   * @return {OO.ui.Process} this
   * @chainable
   */
  OO.ui.Process.prototype.next = function ( step, context ) {
  	this.steps.push( this.createStep( step, context ) );
  	return this;
  };
  
  /**
   * A window instance represents the life cycle for one single opening of a window
   * until its closing.
   *
   * While OO.ui.WindowManager will reuse OO.ui.Window objects, each time a window is
   * opened, a new lifecycle starts.
   *
   * For more information, please see the [OOjs UI documentation on MediaWiki] [1].
   *
   * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows
   *
   * @class
   *
   * @constructor
   */
  OO.ui.WindowInstance = function OOuiWindowInstance() {
  	var deferreds = {
  		opening: $.Deferred(),
  		opened: $.Deferred(),
  		closing: $.Deferred(),
  		closed: $.Deferred()
  	};
  
  	/**
  	 * @private
  	 * @property {Object}
  	 */
  	this.deferreds = deferreds;
  
  	// Set these up as chained promises so that rejecting of
  	// an earlier stage automatically rejects the subsequent
  	// would-be stages as well.
  
  	/**
  	 * @property {jQuery.Promise}
  	 */
  	this.opening = deferreds.opening.promise();
  	/**
  	 * @property {jQuery.Promise}
  	 */
  	this.opened = this.opening.then( function () {
  		return deferreds.opened;
  	} );
  	/**
  	 * @property {jQuery.Promise}
  	 */
  	this.closing = this.opened.then( function () {
  		return deferreds.closing;
  	} );
  	/**
  	 * @property {jQuery.Promise}
  	 */
  	this.closed = this.closing.then( function () {
  		return deferreds.closed;
  	} );
  };
  
  /* Setup */
  
  OO.initClass( OO.ui.WindowInstance );
  
  /**
   * Check if window is opening.
   *
   * @return {boolean} Window is opening
   */
  OO.ui.WindowInstance.prototype.isOpening = function () {
  	return this.deferreds.opened.state() === 'pending';
  };
  
  /**
   * Check if window is opened.
   *
   * @return {boolean} Window is opened
   */
  OO.ui.WindowInstance.prototype.isOpened = function () {
  	return this.deferreds.opened.state() === 'resolved' &&
  		this.deferreds.closing.state() === 'pending';
  };
  
  /**
   * Check if window is closing.
   *
   * @return {boolean} Window is closing
   */
  OO.ui.WindowInstance.prototype.isClosing = function () {
  	return this.deferreds.closing.state() === 'resolved' &&
  		this.deferreds.closed.state() === 'pending';
  };
  
  /**
   * Check if window is closed.
   *
   * @return {boolean} Window is closed
   */
  OO.ui.WindowInstance.prototype.isClosed = function () {
  	return this.deferreds.closed.state() === 'resolved';
  };
  
  /**
   * Window managers are used to open and close {@link OO.ui.Window windows} and control their presentation.
   * Managed windows are mutually exclusive. If a new window is opened while a current window is opening
   * or is opened, the current window will be closed and any ongoing {@link OO.ui.Process process} will be cancelled. Windows
   * themselves are persistent and—rather than being torn down when closed—can be repopulated with the
   * pertinent data and reused.
   *
   * Over the lifecycle of a window, the window manager makes available three promises: `opening`,
   * `opened`, and `closing`, which represent the primary stages of the cycle:
   *
   * **Opening**: the opening stage begins when the window manager’s #openWindow or a window’s
   * {@link OO.ui.Window#open open} method is used, and the window manager begins to open the window.
   *
   * - an `opening` event is emitted with an `opening` promise
   * - the #getSetupDelay method is called and the returned value is used to time a pause in execution before the
   *   window’s {@link OO.ui.Window#method-setup setup} method is called which executes OO.ui.Window#getSetupProcess.
   * - a `setup` progress notification is emitted from the `opening` promise
   * - the #getReadyDelay method is called the returned value is used to time a pause in execution before the
   *   window’s {@link OO.ui.Window#method-ready ready} method is called which executes OO.ui.Window#getReadyProcess.
   * - a `ready` progress notification is emitted from the `opening` promise
   * - the `opening` promise is resolved with an `opened` promise
   *
   * **Opened**: the window is now open.
   *
   * **Closing**: the closing stage begins when the window manager's #closeWindow or the
   * window's {@link OO.ui.Window#close close} methods is used, and the window manager begins
   * to close the window.
   *
   * - the `opened` promise is resolved with `closing` promise and a `closing` event is emitted
   * - the #getHoldDelay method is called and the returned value is used to time a pause in execution before
   *   the window's {@link OO.ui.Window#getHoldProcess getHoldProces} method is called on the
   *   window and its result executed
   * - a `hold` progress notification is emitted from the `closing` promise
   * - the #getTeardownDelay() method is called and the returned value is used to time a pause in execution before
   *   the window's {@link OO.ui.Window#getTeardownProcess getTeardownProcess} method is called on the
   *   window and its result executed
   * - a `teardown` progress notification is emitted from the `closing` promise
   * - the `closing` promise is resolved. The window is now closed
   *
   * See the [OOjs UI documentation on MediaWiki][1] for more information.
   *
   * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
   *
   * @class
   * @extends OO.ui.Element
   * @mixins OO.EventEmitter
   *
   * @constructor
   * @param {Object} [config] Configuration options
   * @cfg {OO.Factory} [factory] Window factory to use for automatic instantiation
   *  Note that window classes that are instantiated with a factory must have
   *  a {@link OO.ui.Dialog#static-name static name} property that specifies a symbolic name.
   * @cfg {boolean} [modal=true] Prevent interaction outside the dialog
   */
  OO.ui.WindowManager = function OoUiWindowManager( config ) {
  	// Configuration initialization
  	config = config || {};
  
  	// Parent constructor
  	OO.ui.WindowManager.parent.call( this, config );
  
  	// Mixin constructors
  	OO.EventEmitter.call( this );
  
  	// Properties
  	this.factory = config.factory;
  	this.modal = config.modal === undefined || !!config.modal;
  	this.windows = {};
  	// Deprecated placeholder promise given to compatOpening in openWindow()
  	// that is resolved in closeWindow().
  	this.compatOpened = null;
  	this.preparingToOpen = null;
  	this.preparingToClose = null;
  	this.currentWindow = null;
  	this.globalEvents = false;
  	this.$returnFocusTo = null;
  	this.$ariaHidden = null;
  	this.onWindowResizeTimeout = null;
  	this.onWindowResizeHandler = this.onWindowResize.bind( this );
  	this.afterWindowResizeHandler = this.afterWindowResize.bind( this );
  
  	// Initialization
  	this.$element
  		.addClass( 'oo-ui-windowManager' )
  		.toggleClass( 'oo-ui-windowManager-modal', this.modal );
  };
  
  /* Setup */
  
  OO.inheritClass( OO.ui.WindowManager, OO.ui.Element );
  OO.mixinClass( OO.ui.WindowManager, OO.EventEmitter );
  
  /* Events */
  
  /**
   * An 'opening' event is emitted when the window begins to be opened.
   *
   * @event opening
   * @param {OO.ui.Window} win Window that's being opened
   * @param {jQuery.Promise} opened A promise resolved with a value when the window is opened successfully.
   *  This promise also emits `setup` and `ready` notifications. When this promise is resolved, the first
   *  argument of the value is an 'closed' promise, the second argument is the opening data.
   * @param {Object} data Window opening data
   */
  
  /**
   * A 'closing' event is emitted when the window begins to be closed.
   *
   * @event closing
   * @param {OO.ui.Window} win Window that's being closed
   * @param {jQuery.Promise} closed A promise resolved with a value when the window is closed successfully.
   *  This promise also emits `hold` and `teardown` notifications. When this promise is resolved, the first
   *  argument of its value is the closing data.
   * @param {Object} data Window closing data
   */
  
  /**
   * A 'resize' event is emitted when a window is resized.
   *
   * @event resize
   * @param {OO.ui.Window} win Window that was resized
   */
  
  /* Static Properties */
  
  /**
   * Map of the symbolic name of each window size and its CSS properties.
   *
   * @static
   * @inheritable
   * @property {Object}
   */
  OO.ui.WindowManager.static.sizes = {
  	small: {
  		width: 300
  	},
  	medium: {
  		width: 500
  	},
  	large: {
  		width: 700
  	},
  	larger: {
  		width: 900
  	},
  	full: {
  		// These can be non-numeric because they are never used in calculations
  		width: '100%',
  		height: '100%'
  	}
  };
  
  /**
   * Symbolic name of the default window size.
   *
   * The default size is used if the window's requested size is not recognized.
   *
   * @static
   * @inheritable
   * @property {string}
   */
  OO.ui.WindowManager.static.defaultSize = 'medium';
  
  /* Methods */
  
  /**
   * Handle window resize events.
   *
   * @private
   * @param {jQuery.Event} e Window resize event
   */
  OO.ui.WindowManager.prototype.onWindowResize = function () {
  	clearTimeout( this.onWindowResizeTimeout );
  	this.onWindowResizeTimeout = setTimeout( this.afterWindowResizeHandler, 200 );
  };
  
  /**
   * Handle window resize events.
   *
   * @private
   * @param {jQuery.Event} e Window resize event
   */
  OO.ui.WindowManager.prototype.afterWindowResize = function () {
  	if ( this.currentWindow ) {
  		this.updateWindowSize( this.currentWindow );
  	}
  };
  
  /**
   * Check if window is opening.
   *
   * @param {OO.ui.Window} win Window to check
   * @return {boolean} Window is opening
   */
  OO.ui.WindowManager.prototype.isOpening = function ( win ) {
  	return win === this.currentWindow && !!this.lifecycle &&
  		this.lifecycle.isOpening();
  };
  
  /**
   * Check if window is closing.
   *
   * @param {OO.ui.Window} win Window to check
   * @return {boolean} Window is closing
   */
  OO.ui.WindowManager.prototype.isClosing = function ( win ) {
  	return win === this.currentWindow && !!this.lifecycle &&
  		this.lifecycle.isClosing();
  };
  
  /**
   * Check if window is opened.
   *
   * @param {OO.ui.Window} win Window to check
   * @return {boolean} Window is opened
   */
  OO.ui.WindowManager.prototype.isOpened = function ( win ) {
  	return win === this.currentWindow && !!this.lifecycle &&
  		this.lifecycle.isOpened();
  };
  
  /**
   * Check if a window is being managed.
   *
   * @param {OO.ui.Window} win Window to check
   * @return {boolean} Window is being managed
   */
  OO.ui.WindowManager.prototype.hasWindow = function ( win ) {
  	var name;
  
  	for ( name in this.windows ) {
  		if ( this.windows[ name ] === win ) {
  			return true;
  		}
  	}
  
  	return false;
  };
  
  /**
   * Get the number of milliseconds to wait after opening begins before executing the ‘setup’ process.
   *
   * @param {OO.ui.Window} win Window being opened
   * @param {Object} [data] Window opening data
   * @return {number} Milliseconds to wait
   */
  OO.ui.WindowManager.prototype.getSetupDelay = function () {
  	return 0;
  };
  
  /**
   * Get the number of milliseconds to wait after setup has finished before executing the ‘ready’ process.
   *
   * @param {OO.ui.Window} win Window being opened
   * @param {Object} [data] Window opening data
   * @return {number} Milliseconds to wait
   */
  OO.ui.WindowManager.prototype.getReadyDelay = function () {
  	return 0;
  };
  
  /**
   * Get the number of milliseconds to wait after closing has begun before executing the 'hold' process.
   *
   * @param {OO.ui.Window} win Window being closed
   * @param {Object} [data] Window closing data
   * @return {number} Milliseconds to wait
   */
  OO.ui.WindowManager.prototype.getHoldDelay = function () {
  	return 0;
  };
  
  /**
   * Get the number of milliseconds to wait after the ‘hold’ process has finished before
   * executing the ‘teardown’ process.
   *
   * @param {OO.ui.Window} win Window being closed
   * @param {Object} [data] Window closing data
   * @return {number} Milliseconds to wait
   */
  OO.ui.WindowManager.prototype.getTeardownDelay = function () {
  	return this.modal ? 250 : 0;
  };
  
  /**
   * Get a window by its symbolic name.
   *
   * If the window is not yet instantiated and its symbolic name is recognized by a factory, it will be
   * instantiated and added to the window manager automatically. Please see the [OOjs UI documentation on MediaWiki][3]
   * for more information about using factories.
   * [3]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
   *
   * @param {string} name Symbolic name of the window
   * @return {jQuery.Promise} Promise resolved with matching window, or rejected with an OO.ui.Error
   * @throws {Error} An error is thrown if the symbolic name is not recognized by the factory.
   * @throws {Error} An error is thrown if the named window is not recognized as a managed window.
   */
  OO.ui.WindowManager.prototype.getWindow = function ( name ) {
  	var deferred = $.Deferred(),
  		win = this.windows[ name ];
  
  	if ( !( win instanceof OO.ui.Window ) ) {
  		if ( this.factory ) {
  			if ( !this.factory.lookup( name ) ) {
  				deferred.reject( new OO.ui.Error(
  					'Cannot auto-instantiate window: symbolic name is unrecognized by the factory'
  				) );
  			} else {
  				win = this.factory.create( name );
  				this.addWindows( [ win ] );
  				deferred.resolve( win );
  			}
  		} else {
  			deferred.reject( new OO.ui.Error(
  				'Cannot get unmanaged window: symbolic name unrecognized as a managed window'
  			) );
  		}
  	} else {
  		deferred.resolve( win );
  	}
  
  	return deferred.promise();
  };
  
  /**
   * Get current window.
   *
   * @return {OO.ui.Window|null} Currently opening/opened/closing window
   */
  OO.ui.WindowManager.prototype.getCurrentWindow = function () {
  	return this.currentWindow;
  };
  
  /**
   * Open a window.
   *
   * @param {OO.ui.Window|string} win Window object or symbolic name of window to open
   * @param {Object} [data] Window opening data
   * @param {jQuery|null} [data.$returnFocusTo] Element to which the window will return focus when closed.
   *  Defaults the current activeElement. If set to null, focus isn't changed on close.
   * @return {OO.ui.WindowInstance|jQuery.Promise} A lifecycle object representing this particular
   *  opening of the window. For backwards-compatibility, then object is also a Thenable that is resolved
   *  when the window is done opening, with nested promise for when closing starts. This behaviour
   *  is deprecated and is not compatible with jQuery 3. See T163510.
   * @fires opening
   */
  OO.ui.WindowManager.prototype.openWindow = function ( win, data, lifecycle, compatOpening ) {
  	var error,
  		manager = this;
  	data = data || {};
  
  	// Internal parameter 'lifecycle' allows this method to always return
  	// a lifecycle even if the window still needs to be created
  	// asynchronously when 'win' is a string.
  	lifecycle = lifecycle || new OO.ui.WindowInstance();
  	compatOpening = compatOpening || $.Deferred();
  
  	// Turn lifecycle into a Thenable for backwards-compatibility with
  	// the deprecated nested-promise behaviour, see T163510.
  	[ 'state', 'always', 'catch', 'pipe', 'then', 'promise', 'progress', 'done', 'fail' ]
  		.forEach( function ( method ) {
  			lifecycle[ method ] = function () {
  				OO.ui.warnDeprecation(
  					'Using the return value of openWindow as a promise is deprecated. ' +
  					'Use .openWindow( ... ).opening.' + method + '( ... ) instead.'
  				);
  				return compatOpening[ method ].apply( this, arguments );
  			};
  		} );
  
  	// Argument handling
  	if ( typeof win === 'string' ) {
  		this.getWindow( win ).then(
  			function ( win ) {
  				manager.openWindow( win, data, lifecycle, compatOpening );
  			},
  			function ( err ) {
  				lifecycle.deferreds.opening.reject( err );
  			}
  		);
  		return lifecycle;
  	}
  
  	// Error handling
  	if ( !this.hasWindow( win ) ) {
  		error = 'Cannot open window: window is not attached to manager';
  	} else if ( this.lifecycle && this.lifecycle.isOpened() ) {
  		error = 'Cannot open window: another window is open';
  	} else if ( this.preparingToOpen || ( this.lifecycle && this.lifecycle.isOpening() ) ) {
  		error = 'Cannot open window: another window is opening';
  	}
  
  	if ( error ) {
  		compatOpening.reject( new OO.ui.Error( error ) );
  		lifecycle.deferreds.opening.reject( new OO.ui.Error( error ) );
  		return lifecycle;
  	}
  
  	// If a window is currently closing, wait for it to complete
  	this.preparingToOpen = $.when( this.lifecycle && this.lifecycle.closed );
  	// Ensure handlers get called after preparingToOpen is set
  	this.preparingToOpen.done( function () {
  		if ( manager.modal ) {
  			manager.toggleGlobalEvents( true );
  			manager.toggleAriaIsolation( true );
  		}
  		manager.$returnFocusTo = data.$returnFocusTo !== undefined ? data.$returnFocusTo : $( document.activeElement );
  		manager.currentWindow = win;
  		manager.lifecycle = lifecycle;
  		manager.preparingToOpen = null;
  		manager.emit( 'opening', win, compatOpening, data );
  		lifecycle.deferreds.opening.resolve( data );
  		setTimeout( function () {
  			manager.compatOpened = $.Deferred();
  			win.setup( data ).then( function () {
  				manager.updateWindowSize( win );
  				compatOpening.notify( { state: 'setup' } );
  				setTimeout( function () {
  					win.ready( data ).then( function () {
  						compatOpening.notify( { state: 'ready' } );
  						lifecycle.deferreds.opened.resolve( data );
  						compatOpening.resolve( manager.compatOpened.promise(), data );
  					}, function () {
  						lifecycle.deferreds.opened.reject();
  						compatOpening.reject();
  						manager.closeWindow( win );
  					} );
  				}, manager.getReadyDelay() );
  			}, function () {
  				lifecycle.deferreds.opened.reject();
  				compatOpening.reject();
  				manager.closeWindow( win );
  			} );
  		}, manager.getSetupDelay() );
  	} );
  
  	return lifecycle;
  };
  
  /**
   * Close a window.
   *
   * @param {OO.ui.Window|string} win Window object or symbolic name of window to close
   * @param {Object} [data] Window closing data
   * @return {OO.ui.WindowInstance|jQuery.Promise} A lifecycle object representing this particular
   *  opening of the window. For backwards-compatibility, the object is also a Thenable that is resolved
   *  when the window is done closing, see T163510.
   * @fires closing
   */
  OO.ui.WindowManager.prototype.closeWindow = function ( win, data ) {
  	var error,
  		manager = this,
  		compatClosing = $.Deferred(),
  		lifecycle = this.lifecycle,
  		compatOpened;
  
  	// Argument handling
  	if ( typeof win === 'string' ) {
  		win = this.windows[ win ];
  	} else if ( !this.hasWindow( win ) ) {
  		win = null;
  	}
  
  	// Error handling
  	if ( !lifecycle ) {
  		error = 'Cannot close window: no window is currently open';
  	} else if ( !win ) {
  		error = 'Cannot close window: window is not attached to manager';
  	} else if ( win !== this.currentWindow || this.lifecycle.isClosed() ) {
  		error = 'Cannot close window: window already closed with different data';
  	} else if ( this.preparingToClose || this.lifecycle.isClosing() ) {
  		error = 'Cannot close window: window already closing with different data';
  	}
  
  	if ( error ) {
  		// This function was called for the wrong window and we don't want to mess with the current
  		// window's state.
  		lifecycle = new OO.ui.WindowInstance();
  		// Pretend the window has been opened, so that we can pretend to fail to close it.
  		lifecycle.deferreds.opening.resolve( {} );
  		lifecycle.deferreds.opened.resolve( {} );
  	}
  
  	// Turn lifecycle into a Thenable for backwards-compatibility with
  	// the deprecated nested-promise behaviour, see T163510.
  	[ 'state', 'always', 'catch', 'pipe', 'then', 'promise', 'progress', 'done', 'fail' ]
  		.forEach( function ( method ) {
  			lifecycle[ method ] = function () {
  				OO.ui.warnDeprecation(
  					'Using the return value of closeWindow as a promise is deprecated. ' +
  					'Use .closeWindow( ... ).closed.' + method + '( ... ) instead.'
  				);
  				return compatClosing[ method ].apply( this, arguments );
  			};
  		} );
  
  	if ( error ) {
  		compatClosing.reject( new OO.ui.Error( error ) );
  		lifecycle.deferreds.closing.reject( new OO.ui.Error( error ) );
  		return lifecycle;
  	}
  
  	// If the window is currently opening, close it when it's done
  	this.preparingToClose = $.when( this.lifecycle.opened );
  	// Ensure handlers get called after preparingToClose is set
  	this.preparingToClose.always( function () {
  		manager.preparingToClose = null;
  		manager.emit( 'closing', win, compatClosing, data );
  		lifecycle.deferreds.closing.resolve( data );
  		compatOpened = manager.compatOpened;
  		manager.compatOpened = null;
  		compatOpened.resolve( compatClosing.promise(), data );
  		setTimeout( function () {
  			win.hold( data ).then( function () {
  				compatClosing.notify( { state: 'hold' } );
  				setTimeout( function () {
  					win.teardown( data ).then( function () {
  						compatClosing.notify( { state: 'teardown' } );
  						if ( manager.modal ) {
  							manager.toggleGlobalEvents( false );
  							manager.toggleAriaIsolation( false );
  						}
  						if ( manager.$returnFocusTo && manager.$returnFocusTo.length ) {
  							manager.$returnFocusTo[ 0 ].focus();
  						}
  						manager.currentWindow = null;
  						manager.lifecycle = null;
  						lifecycle.deferreds.closed.resolve( data );
  						compatClosing.resolve( data );
  					} );
  				}, manager.getTeardownDelay() );
  			} );
  		}, manager.getHoldDelay() );
  	} );
  
  	return lifecycle;
  };
  
  /**
   * Add windows to the window manager.
   *
   * Windows can be added by reference, symbolic name, or explicitly defined symbolic names.
   * See the [OOjs ui documentation on MediaWiki] [2] for examples.
   * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
   *
   * This function can be called in two manners:
   *
   * 1. `.addWindows( [ windowA, windowB, ... ] )` (where `windowA`, `windowB` are OO.ui.Window objects)
   *
   *    This syntax registers windows under the symbolic names defined in their `.static.name`
   *    properties. For example, if `windowA.constructor.static.name` is `'nameA'`, calling
   *    `.openWindow( 'nameA' )` afterwards will open the window `windowA`. This syntax requires the
   *    static name to be set, otherwise an exception will be thrown.
   *
   *    This is the recommended way, as it allows for an easier switch to using a window factory.
   *
   * 2. `.addWindows( { nameA: windowA, nameB: windowB, ... } )`
   *
   *    This syntax registers windows under the explicitly given symbolic names. In this example,
   *    calling `.openWindow( 'nameA' )` afterwards will open the window `windowA`, regardless of what
   *    its `.static.name` is set to. The static name is not required to be set.
   *
   *    This should only be used if you need to override the default symbolic names.
   *
   * Example:
   *
   *     var windowManager = new OO.ui.WindowManager();
   *     $( 'body' ).append( windowManager.$element );
   *
   *     // Add a window under the default name: see OO.ui.MessageDialog.static.name
   *     windowManager.addWindows( [ new OO.ui.MessageDialog() ] );
   *     // Add a window under an explicit name
   *     windowManager.addWindows( { myMessageDialog: new OO.ui.MessageDialog() } );
   *
   *     // Open window by default name
   *     windowManager.openWindow( 'message' );
   *     // Open window by explicitly given name
   *     windowManager.openWindow( 'myMessageDialog' );
   *
   *
   * @param {Object.<string,OO.ui.Window>|OO.ui.Window[]} windows An array of window objects specified
   *  by reference, symbolic name, or explicitly defined symbolic names.
   * @throws {Error} An error is thrown if a window is added by symbolic name, but has neither an
   *  explicit nor a statically configured symbolic name.
   */
  OO.ui.WindowManager.prototype.addWindows = function ( windows ) {
  	var i, len, win, name, list;
  
  	if ( Array.isArray( windows ) ) {
  		// Convert to map of windows by looking up symbolic names from static configuration
  		list = {};
  		for ( i = 0, len = windows.length; i < len; i++ ) {
  			name = windows[ i ].constructor.static.name;
  			if ( !name ) {
  				throw new Error( 'Windows must have a `name` static property defined.' );
  			}
  			list[ name ] = windows[ i ];
  		}
  	} else if ( OO.isPlainObject( windows ) ) {
  		list = windows;
  	}
  
  	// Add windows
  	for ( name in list ) {
  		win = list[ name ];
  		this.windows[ name ] = win.toggle( false );
  		this.$element.append( win.$element );
  		win.setManager( this );
  	}
  };
  
  /**
   * Remove the specified windows from the windows manager.
   *
   * Windows will be closed before they are removed. If you wish to remove all windows, you may wish to use
   * the #clearWindows method instead. If you no longer need the window manager and want to ensure that it no
   * longer listens to events, use the #destroy method.
   *
   * @param {string[]} names Symbolic names of windows to remove
   * @return {jQuery.Promise} Promise resolved when window is closed and removed
   * @throws {Error} An error is thrown if the named windows are not managed by the window manager.
   */
  OO.ui.WindowManager.prototype.removeWindows = function ( names ) {
  	var i, len, win, name, cleanupWindow,
  		manager = this,
  		promises = [],
  		cleanup = function ( name, win ) {
  			delete manager.windows[ name ];
  			win.$element.detach();
  		};
  
  	for ( i = 0, len = names.length; i < len; i++ ) {
  		name = names[ i ];
  		win = this.windows[ name ];
  		if ( !win ) {
  			throw new Error( 'Cannot remove window' );
  		}
  		cleanupWindow = cleanup.bind( null, name, win );
  		promises.push( this.closeWindow( name ).closed.then( cleanupWindow, cleanupWindow ) );
  	}
  
  	return $.when.apply( $, promises );
  };
  
  /**
   * Remove all windows from the window manager.
   *
   * Windows will be closed before they are removed. Note that the window manager, though not in use, will still
   * listen to events. If the window manager will not be used again, you may wish to use the #destroy method instead.
   * To remove just a subset of windows, use the #removeWindows method.
   *
   * @return {jQuery.Promise} Promise resolved when all windows are closed and removed
   */
  OO.ui.WindowManager.prototype.clearWindows = function () {
  	return this.removeWindows( Object.keys( this.windows ) );
  };
  
  /**
   * Set dialog size. In general, this method should not be called directly.
   *
   * Fullscreen mode will be used if the dialog is too wide to fit in the screen.
   *
   * @param {OO.ui.Window} win Window to update, should be the current window
   * @chainable
   */
  OO.ui.WindowManager.prototype.updateWindowSize = function ( win ) {
  	var isFullscreen;
  
  	// Bypass for non-current, and thus invisible, windows
  	if ( win !== this.currentWindow ) {
  		return;
  	}
  
  	isFullscreen = win.getSize() === 'full';
  
  	this.$element.toggleClass( 'oo-ui-windowManager-fullscreen', isFullscreen );
  	this.$element.toggleClass( 'oo-ui-windowManager-floating', !isFullscreen );
  	win.setDimensions( win.getSizeProperties() );
  
  	this.emit( 'resize', win );
  
  	return this;
  };
  
  /**
   * Bind or unbind global events for scrolling.
   *
   * @private
   * @param {boolean} [on] Bind global events
   * @chainable
   */
  OO.ui.WindowManager.prototype.toggleGlobalEvents = function ( on ) {
  	var scrollWidth, bodyMargin,
  		$body = $( this.getElementDocument().body ),
  		// We could have multiple window managers open so only modify
  		// the body css at the bottom of the stack
  		stackDepth = $body.data( 'windowManagerGlobalEvents' ) || 0;
  
  	on = on === undefined ? !!this.globalEvents : !!on;
  
  	if ( on ) {
  		if ( !this.globalEvents ) {
  			$( this.getElementWindow() ).on( {
  				// Start listening for top-level window dimension changes
  				'orientationchange resize': this.onWindowResizeHandler
  			} );
  			if ( stackDepth === 0 ) {
  				scrollWidth = window.innerWidth - document.documentElement.clientWidth;
  				bodyMargin = parseFloat( $body.css( 'margin-right' ) ) || 0;
  				$body.css( {
  					overflow: 'hidden',
  					'margin-right': bodyMargin + scrollWidth
  				} );
  			}
  			stackDepth++;
  			this.globalEvents = true;
  		}
  	} else if ( this.globalEvents ) {
  		$( this.getElementWindow() ).off( {
  			// Stop listening for top-level window dimension changes
  			'orientationchange resize': this.onWindowResizeHandler
  		} );
  		stackDepth--;
  		if ( stackDepth === 0 ) {
  			$body.css( {
  				overflow: '',
  				'margin-right': ''
  			} );
  		}
  		this.globalEvents = false;
  	}
  	$body.data( 'windowManagerGlobalEvents', stackDepth );
  
  	return this;
  };
  
  /**
   * Toggle screen reader visibility of content other than the window manager.
   *
   * @private
   * @param {boolean} [isolate] Make only the window manager visible to screen readers
   * @chainable
   */
  OO.ui.WindowManager.prototype.toggleAriaIsolation = function ( isolate ) {
  	isolate = isolate === undefined ? !this.$ariaHidden : !!isolate;
  
  	if ( isolate ) {
  		if ( !this.$ariaHidden ) {
  			// Hide everything other than the window manager from screen readers
  			this.$ariaHidden = $( 'body' )
  				.children()
  				.not( this.$element.parentsUntil( 'body' ).last() )
  				.attr( 'aria-hidden', '' );
  		}
  	} else if ( this.$ariaHidden ) {
  		// Restore screen reader visibility
  		this.$ariaHidden.removeAttr( 'aria-hidden' );
  		this.$ariaHidden = null;
  	}
  
  	return this;
  };
  
  /**
   * Destroy the window manager.
   *
   * Destroying the window manager ensures that it will no longer listen to events. If you would like to
   * continue using the window manager, but wish to remove all windows from it, use the #clearWindows method
   * instead.
   */
  OO.ui.WindowManager.prototype.destroy = function () {
  	this.toggleGlobalEvents( false );
  	this.toggleAriaIsolation( false );
  	this.clearWindows();
  	this.$element.remove();
  };
  
  /**
   * A window is a container for elements that are in a child frame. They are used with
   * a window manager (OO.ui.WindowManager), which is used to open and close the window and control
   * its presentation. The size of a window is specified using a symbolic name (e.g., ‘small’, ‘medium’,
   * ‘large’), which is interpreted by the window manager. If the requested size is not recognized,
   * the window manager will choose a sensible fallback.
   *
   * The lifecycle of a window has three primary stages (opening, opened, and closing) in which
   * different processes are executed:
   *
   * **opening**: The opening stage begins when the window manager's {@link OO.ui.WindowManager#openWindow
   * openWindow} or the window's {@link #open open} methods are used, and the window manager begins to open
   * the window.
   *
   * - {@link #getSetupProcess} method is called and its result executed
   * - {@link #getReadyProcess} method is called and its result executed
   *
   * **opened**: The window is now open
   *
   * **closing**: The closing stage begins when the window manager's
   * {@link OO.ui.WindowManager#closeWindow closeWindow}
   * or the window's {@link #close} methods are used, and the window manager begins to close the window.
   *
   * - {@link #getHoldProcess} method is called and its result executed
   * - {@link #getTeardownProcess} method is called and its result executed. The window is now closed
   *
   * Each of the window's processes (setup, ready, hold, and teardown) can be extended in subclasses
   * by overriding the window's #getSetupProcess, #getReadyProcess, #getHoldProcess and #getTeardownProcess
   * methods. Note that each {@link OO.ui.Process process} is executed in series, so asynchronous
   * processing can complete. Always assume window processes are executed asynchronously.
   *
   * For more information, please see the [OOjs UI documentation on MediaWiki] [1].
   *
   * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows
   *
   * @abstract
   * @class
   * @extends OO.ui.Element
   * @mixins OO.EventEmitter
   *
   * @constructor
   * @param {Object} [config] Configuration options
   * @cfg {string} [size] Symbolic name of the dialog size: `small`, `medium`, `large`, `larger` or
   *  `full`.  If omitted, the value of the {@link #static-size static size} property will be used.
   */
  OO.ui.Window = function OoUiWindow( config ) {
  	// Configuration initialization
  	config = config || {};
  
  	// Parent constructor
  	OO.ui.Window.parent.call( this, config );
  
  	// Mixin constructors
  	OO.EventEmitter.call( this );
  
  	// Properties
  	this.manager = null;
  	this.size = config.size || this.constructor.static.size;
  	this.$frame = $( '<div>' );
  	/**
  	 * Overlay element to use for the `$overlay` configuration option of widgets that support it.
  	 * Things put inside of it are overlaid on top of the window and are not bound to its dimensions.
  	 * See <https://www.mediawiki.org/wiki/OOjs_UI/Concepts#Overlays>.
  	 *
  	 *     MyDialog.prototype.initialize = function () {
  	 *       ...
  	 *       var popupButton = new OO.ui.PopupButtonWidget( {
  	 *         $overlay: this.$overlay,
  	 *         label: 'Popup button',
  	 *         popup: {
  	 *           $content: $( '<p>Popup contents.</p><p>Popup contents.</p><p>Popup contents.</p>' ),
  	 *           padded: true
  	 *         }
  	 *       } );
  	 *       ...
  	 *     };
  	 *
  	 * @property {jQuery}
  	 */
  	this.$overlay = $( '<div>' );
  	this.$content = $( '<div>' );
  
  	this.$focusTrapBefore = $( '<div>' ).prop( 'tabIndex', 0 );
  	this.$focusTrapAfter = $( '<div>' ).prop( 'tabIndex', 0 );
  	this.$focusTraps = this.$focusTrapBefore.add( this.$focusTrapAfter );
  
  	// Initialization
  	this.$overlay.addClass( 'oo-ui-window-overlay' );
  	this.$content
  		.addClass( 'oo-ui-window-content' )
  		.attr( 'tabindex', 0 );
  	this.$frame
  		.addClass( 'oo-ui-window-frame' )
  		.append( this.$focusTrapBefore, this.$content, this.$focusTrapAfter );
  
  	this.$element
  		.addClass( 'oo-ui-window' )
  		.append( this.$frame, this.$overlay );
  
  	// Initially hidden - using #toggle may cause errors if subclasses override toggle with methods
  	// that reference properties not initialized at that time of parent class construction
  	// TODO: Find a better way to handle post-constructor setup
  	this.visible = false;
  	this.$element.addClass( 'oo-ui-element-hidden' );
  };
  
  /* Setup */
  
  OO.inheritClass( OO.ui.Window, OO.ui.Element );
  OO.mixinClass( OO.ui.Window, OO.EventEmitter );
  
  /* Static Properties */
  
  /**
   * Symbolic name of the window size: `small`, `medium`, `large`, `larger` or `full`.
   *
   * The static size is used if no #size is configured during construction.
   *
   * @static
   * @inheritable
   * @property {string}
   */
  OO.ui.Window.static.size = 'medium';
  
  /* Methods */
  
  /**
   * Handle mouse down events.
   *
   * @private
   * @param {jQuery.Event} e Mouse down event
   */
  OO.ui.Window.prototype.onMouseDown = function ( e ) {
  	// Prevent clicking on the click-block from stealing focus
  	if ( e.target === this.$element[ 0 ] ) {
  		return false;
  	}
  };
  
  /**
   * Check if the window has been initialized.
   *
   * Initialization occurs when a window is added to a manager.
   *
   * @return {boolean} Window has been initialized
   */
  OO.ui.Window.prototype.isInitialized = function () {
  	return !!this.manager;
  };
  
  /**
   * Check if the window is visible.
   *
   * @return {boolean} Window is visible
   */
  OO.ui.Window.prototype.isVisible = function () {
  	return this.visible;
  };
  
  /**
   * Check if the window is opening.
   *
   * This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isOpening isOpening}
   * method.
   *
   * @return {boolean} Window is opening
   */
  OO.ui.Window.prototype.isOpening = function () {
  	return this.manager.isOpening( this );
  };
  
  /**
   * Check if the window is closing.
   *
   * This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isClosing isClosing} method.
   *
   * @return {boolean} Window is closing
   */
  OO.ui.Window.prototype.isClosing = function () {
  	return this.manager.isClosing( this );
  };
  
  /**
   * Check if the window is opened.
   *
   * This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isOpened isOpened} method.
   *
   * @return {boolean} Window is opened
   */
  OO.ui.Window.prototype.isOpened = function () {
  	return this.manager.isOpened( this );
  };
  
  /**
   * Get the window manager.
   *
   * All windows must be attached to a window manager, which is used to open
   * and close the window and control its presentation.
   *
   * @return {OO.ui.WindowManager} Manager of window
   */
  OO.ui.Window.prototype.getManager = function () {
  	return this.manager;
  };
  
  /**
   * Get the symbolic name of the window size (e.g., `small` or `medium`).
   *
   * @return {string} Symbolic name of the size: `small`, `medium`, `large`, `larger`, `full`
   */
  OO.ui.Window.prototype.getSize = function () {
  	var viewport = OO.ui.Element.static.getDimensions( this.getElementWindow() ),
  		sizes = this.manager.constructor.static.sizes,
  		size = this.size;
  
  	if ( !sizes[ size ] ) {
  		size = this.manager.constructor.static.defaultSize;
  	}
  	if ( size !== 'full' && viewport.rect.right - viewport.rect.left < sizes[ size ].width ) {
  		size = 'full';
  	}
  
  	return size;
  };
  
  /**
   * Get the size properties associated with the current window size
   *
   * @return {Object} Size properties
   */
  OO.ui.Window.prototype.getSizeProperties = function () {
  	return this.manager.constructor.static.sizes[ this.getSize() ];
  };
  
  /**
   * Disable transitions on window's frame for the duration of the callback function, then enable them
   * back.
   *
   * @private
   * @param {Function} callback Function to call while transitions are disabled
   */
  OO.ui.Window.prototype.withoutSizeTransitions = function ( callback ) {
  	// Temporarily resize the frame so getBodyHeight() can use scrollHeight measurements.
  	// Disable transitions first, otherwise we'll get values from when the window was animating.
  	// We need to build the transition CSS properties using these specific properties since
  	// Firefox doesn't return anything useful when asked just for 'transition'.
  	var oldTransition = this.$frame.css( 'transition-property' ) + ' ' +
  		this.$frame.css( 'transition-duration' ) + ' ' +
  		this.$frame.css( 'transition-timing-function' ) + ' ' +
  		this.$frame.css( 'transition-delay' );
  
  	this.$frame.css( 'transition', 'none' );
  	callback();
  
  	// Force reflow to make sure the style changes done inside callback
  	// really are not transitioned
  	this.$frame.height();
  	this.$frame.css( 'transition', oldTransition );
  };
  
  /**
   * Get the height of the full window contents (i.e., the window head, body and foot together).
   *
   * What consistitutes the head, body, and foot varies depending on the window type.
   * A {@link OO.ui.MessageDialog message dialog} displays a title and message in its body,
   * and any actions in the foot. A {@link OO.ui.ProcessDialog process dialog} displays a title
   * and special actions in the head, and dialog content in the body.
   *
   * To get just the height of the dialog body, use the #getBodyHeight method.
   *
   * @return {number} The height of the window contents (the dialog head, body and foot) in pixels
   */
  OO.ui.Window.prototype.getContentHeight = function () {
  	var bodyHeight,
  		win = this,
  		bodyStyleObj = this.$body[ 0 ].style,
  		frameStyleObj = this.$frame[ 0 ].style;
  
  	// Temporarily resize the frame so getBodyHeight() can use scrollHeight measurements.
  	// Disable transitions first, otherwise we'll get values from when the window was animating.
  	this.withoutSizeTransitions( function () {
  		var oldHeight = frameStyleObj.height,
  			oldPosition = bodyStyleObj.position;
  		frameStyleObj.height = '1px';
  		// Force body to resize to new width
  		bodyStyleObj.position = 'relative';
  		bodyHeight = win.getBodyHeight();
  		frameStyleObj.height = oldHeight;
  		bodyStyleObj.position = oldPosition;
  	} );
  
  	return (
  		// Add buffer for border
  		( this.$frame.outerHeight() - this.$frame.innerHeight() ) +
  		// Use combined heights of children
  		( this.$head.outerHeight( true ) + bodyHeight + this.$foot.outerHeight( true ) )
  	);
  };
  
  /**
   * Get the height of the window body.
   *
   * To get the height of the full window contents (the window body, head, and foot together),
   * use #getContentHeight.
   *
   * When this function is called, the window will temporarily have been resized
   * to height=1px, so .scrollHeight measurements can be taken accurately.
   *
   * @return {number} Height of the window body in pixels
   */
  OO.ui.Window.prototype.getBodyHeight = function () {
  	return this.$body[ 0 ].scrollHeight;
  };
  
  /**
   * Get the directionality of the frame (right-to-left or left-to-right).
   *
   * @return {string} Directionality: `'ltr'` or `'rtl'`
   */
  OO.ui.Window.prototype.getDir = function () {
  	return OO.ui.Element.static.getDir( this.$content ) || 'ltr';
  };
  
  /**
   * Get the 'setup' process.
   *
   * The setup process is used to set up a window for use in a particular context,
   * based on the `data` argument. This method is called during the opening phase of the window’s
   * lifecycle.
   *
   * Override this method to add additional steps to the ‘setup’ process the parent method provides
   * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods
   * of OO.ui.Process.
   *
   * To add window content that persists between openings, you may wish to use the #initialize method
   * instead.
   *
   * @param {Object} [data] Window opening data
   * @return {OO.ui.Process} Setup process
   */
  OO.ui.Window.prototype.getSetupProcess = function () {
  	return new OO.ui.Process();
  };
  
  /**
   * Get the ‘ready’ process.
   *
   * The ready process is used to ready a window for use in a particular
   * context, based on the `data` argument. This method is called during the opening phase of
   * the window’s lifecycle, after the window has been {@link #getSetupProcess setup}.
   *
   * Override this method to add additional steps to the ‘ready’ process the parent method
   * provides using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next}
   * methods of OO.ui.Process.
   *
   * @param {Object} [data] Window opening data
   * @return {OO.ui.Process} Ready process
   */
  OO.ui.Window.prototype.getReadyProcess = function () {
  	return new OO.ui.Process();
  };
  
  /**
   * Get the 'hold' process.
   *
   * The hold process is used to keep a window from being used in a particular context,
   * based on the `data` argument. This method is called during the closing phase of the window’s
   * lifecycle.
   *
   * Override this method to add additional steps to the 'hold' process the parent method provides
   * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods
   * of OO.ui.Process.
   *
   * @param {Object} [data] Window closing data
   * @return {OO.ui.Process} Hold process
   */
  OO.ui.Window.prototype.getHoldProcess = function () {
  	return new OO.ui.Process();
  };
  
  /**
   * Get the ‘teardown’ process.
   *
   * The teardown process is used to teardown a window after use. During teardown,
   * user interactions within the window are conveyed and the window is closed, based on the `data`
   * argument. This method is called during the closing phase of the window’s lifecycle.
   *
   * Override this method to add additional steps to the ‘teardown’ process the parent method provides
   * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods
   * of OO.ui.Process.
   *
   * @param {Object} [data] Window closing data
   * @return {OO.ui.Process} Teardown process
   */
  OO.ui.Window.prototype.getTeardownProcess = function () {
  	return new OO.ui.Process();
  };
  
  /**
   * Set the window manager.
   *
   * This will cause the window to initialize. Calling it more than once will cause an error.
   *
   * @param {OO.ui.WindowManager} manager Manager for this window
   * @throws {Error} An error is thrown if the method is called more than once
   * @chainable
   */
  OO.ui.Window.prototype.setManager = function ( manager ) {
  	if ( this.manager ) {
  		throw new Error( 'Cannot set window manager, window already has a manager' );
  	}
  
  	this.manager = manager;
  	this.initialize();
  
  	return this;
  };
  
  /**
   * Set the window size by symbolic name (e.g., 'small' or 'medium')
   *
   * @param {string} size Symbolic name of size: `small`, `medium`, `large`, `larger` or
   *  `full`
   * @chainable
   */
  OO.ui.Window.prototype.setSize = function ( size ) {
  	this.size = size;
  	this.updateSize();
  	return this;
  };
  
  /**
   * Update the window size.
   *
   * @throws {Error} An error is thrown if the window is not attached to a window manager
   * @chainable
   */
  OO.ui.Window.prototype.updateSize = function () {
  	if ( !this.manager ) {
  		throw new Error( 'Cannot update window size, must be attached to a manager' );
  	}
  
  	this.manager.updateWindowSize( this );
  
  	return this;
  };
  
  /**
   * Set window dimensions. This method is called by the {@link OO.ui.WindowManager window manager}
   * when the window is opening. In general, setDimensions should not be called directly.
   *
   * To set the size of the window, use the #setSize method.
   *
   * @param {Object} dim CSS dimension properties
   * @param {string|number} [dim.width] Width
   * @param {string|number} [dim.minWidth] Minimum width
   * @param {string|number} [dim.maxWidth] Maximum width
   * @param {string|number} [dim.height] Height, omit to set based on height of contents
   * @param {string|number} [dim.minHeight] Minimum height
   * @param {string|number} [dim.maxHeight] Maximum height
   * @chainable
   */
  OO.ui.Window.prototype.setDimensions = function ( dim ) {
  	var height,
  		win = this,
  		styleObj = this.$frame[ 0 ].style;
  
  	// Calculate the height we need to set using the correct width
  	if ( dim.height === undefined ) {
  		this.withoutSizeTransitions( function () {
  			var oldWidth = styleObj.width;
  			win.$frame.css( 'width', dim.width || '' );
  			height = win.getContentHeight();
  			styleObj.width = oldWidth;
  		} );
  	} else {
  		height = dim.height;
  	}
  
  	this.$frame.css( {
  		width: dim.width || '',
  		minWidth: dim.minWidth || '',
  		maxWidth: dim.maxWidth || '',
  		height: height || '',
  		minHeight: dim.minHeight || '',
  		maxHeight: dim.maxHeight || ''
  	} );
  
  	return this;
  };
  
  /**
   * Initialize window contents.
   *
   * Before the window is opened for the first time, #initialize is called so that content that
   * persists between openings can be added to the window.
   *
   * To set up a window with new content each time the window opens, use #getSetupProcess.
   *
   * @throws {Error} An error is thrown if the window is not attached to a window manager
   * @chainable
   */
  OO.ui.Window.prototype.initialize = function () {
  	if ( !this.manager ) {
  		throw new Error( 'Cannot initialize window, must be attached to a manager' );
  	}
  
  	// Properties
  	this.$head = $( '<div>' );
  	this.$body = $( '<div>' );
  	this.$foot = $( '<div>' );
  	this.$document = $( this.getElementDocument() );
  
  	// Events
  	this.$element.on( 'mousedown', this.onMouseDown.bind( this ) );
  
  	// Initialization
  	this.$head.addClass( 'oo-ui-window-head' );
  	this.$body.addClass( 'oo-ui-window-body' );
  	this.$foot.addClass( 'oo-ui-window-foot' );
  	this.$content.append( this.$head, this.$body, this.$foot );
  
  	return this;
  };
  
  /**
   * Called when someone tries to focus the hidden element at the end of the dialog.
   * Sends focus back to the start of the dialog.
   *
   * @param {jQuery.Event} event Focus event
   */
  OO.ui.Window.prototype.onFocusTrapFocused = function ( event ) {
  	var backwards = this.$focusTrapBefore.is( event.target ),
  		element = OO.ui.findFocusable( this.$content, backwards );
  	if ( element ) {
  		// There's a focusable element inside the content, at the front or
  		// back depending on which focus trap we hit; select it.
  		element.focus();
  	} else {
  		// There's nothing focusable inside the content. As a fallback,
  		// this.$content is focusable, and focusing it will keep our focus
  		// properly trapped. It's not a *meaningful* focus, since it's just
  		// the content-div for the Window, but it's better than letting focus
  		// escape into the page.
  		this.$content.focus();
  	}
  };
  
  /**
   * Open the window.
   *
   * This method is a wrapper around a call to the window manager’s {@link OO.ui.WindowManager#openWindow openWindow}
   * method, which returns a promise resolved when the window is done opening.
   *
   * To customize the window each time it opens, use #getSetupProcess or #getReadyProcess.
   *
   * @param {Object} [data] Window opening data
   * @return {jQuery.Promise} Promise resolved with a value when the window is opened, or rejected
   *  if the window fails to open. When the promise is resolved successfully, the first argument of the
   *  value is a new promise, which is resolved when the window begins closing.
   * @throws {Error} An error is thrown if the window is not attached to a window manager
   */
  OO.ui.Window.prototype.open = function ( data ) {
  	if ( !this.manager ) {
  		throw new Error( 'Cannot open window, must be attached to a manager' );
  	}
  
  	return this.manager.openWindow( this, data );
  };
  
  /**
   * Close the window.
   *
   * This method is a wrapper around a call to the window
   * manager’s {@link OO.ui.WindowManager#closeWindow closeWindow} method,
   * which returns a closing promise resolved when the window is done closing.
   *
   * The window's #getHoldProcess and #getTeardownProcess methods are called during the closing
   * phase of the window’s lifecycle and can be used to specify closing behavior each time
   * the window closes.
   *
   * @param {Object} [data] Window closing data
   * @return {jQuery.Promise} Promise resolved when window is closed
   * @throws {Error} An error is thrown if the window is not attached to a window manager
   */
  OO.ui.Window.prototype.close = function ( data ) {
  	if ( !this.manager ) {
  		throw new Error( 'Cannot close window, must be attached to a manager' );
  	}
  
  	return this.manager.closeWindow( this, data );
  };
  
  /**
   * Setup window.
   *
   * This is called by OO.ui.WindowManager during window opening, and should not be called directly
   * by other systems.
   *
   * @param {Object} [data] Window opening data
   * @return {jQuery.Promise} Promise resolved when window is setup
   */
  OO.ui.Window.prototype.setup = function ( data ) {
  	var win = this;
  
  	this.toggle( true );
  
  	this.focusTrapHandler = OO.ui.bind( this.onFocusTrapFocused, this );
  	this.$focusTraps.on( 'focus', this.focusTrapHandler );
  
  	return this.getSetupProcess( data ).execute().then( function () {
  		// Force redraw by asking the browser to measure the elements' widths
  		win.$element.addClass( 'oo-ui-window-active oo-ui-window-setup' ).width();
  		win.$content.addClass( 'oo-ui-window-content-setup' ).width();
  	} );
  };
  
  /**
   * Ready window.
   *
   * This is called by OO.ui.WindowManager during window opening, and should not be called directly
   * by other systems.
   *
   * @param {Object} [data] Window opening data
   * @return {jQuery.Promise} Promise resolved when window is ready
   */
  OO.ui.Window.prototype.ready = function ( data ) {
  	var win = this;
  
  	this.$content.focus();
  	return this.getReadyProcess( data ).execute().then( function () {
  		// Force redraw by asking the browser to measure the elements' widths
  		win.$element.addClass( 'oo-ui-window-ready' ).width();
  		win.$content.addClass( 'oo-ui-window-content-ready' ).width();
  	} );
  };
  
  /**
   * Hold window.
   *
   * This is called by OO.ui.WindowManager during window closing, and should not be called directly
   * by other systems.
   *
   * @param {Object} [data] Window closing data
   * @return {jQuery.Promise} Promise resolved when window is held
   */
  OO.ui.Window.prototype.hold = function ( data ) {
  	var win = this;
  
  	return this.getHoldProcess( data ).execute().then( function () {
  		// Get the focused element within the window's content
  		var $focus = win.$content.find( OO.ui.Element.static.getDocument( win.$content ).activeElement );
  
  		// Blur the focused element
  		if ( $focus.length ) {
  			$focus[ 0 ].blur();
  		}
  
  		// Force redraw by asking the browser to measure the elements' widths
  		win.$element.removeClass( 'oo-ui-window-ready' ).width();
  		win.$content.removeClass( 'oo-ui-window-content-ready' ).width();
  	} );
  };
  
  /**
   * Teardown window.
   *
   * This is called by OO.ui.WindowManager during window closing, and should not be called directly
   * by other systems.
   *
   * @param {Object} [data] Window closing data
   * @return {jQuery.Promise} Promise resolved when window is torn down
   */
  OO.ui.Window.prototype.teardown = function ( data ) {
  	var win = this;
  
  	return this.getTeardownProcess( data ).execute().then( function () {
  		// Force redraw by asking the browser to measure the elements' widths
  		win.$element.removeClass( 'oo-ui-window-active oo-ui-window-setup' ).width();
  		win.$content.removeClass( 'oo-ui-window-content-setup' ).width();
  		win.$focusTraps.off( 'focus', win.focusTrapHandler );
  		win.toggle( false );
  	} );
  };
  
  /**
   * The Dialog class serves as the base class for the other types of dialogs.
   * Unless extended to include controls, the rendered dialog box is a simple window
   * that users can close by hitting the ‘Esc’ key. Dialog windows are used with OO.ui.WindowManager,
   * which opens, closes, and controls the presentation of the window. See the
   * [OOjs UI documentation on MediaWiki] [1] for more information.
   *
   *     @example
   *     // A simple dialog window.
   *     function MyDialog( config ) {
   *         MyDialog.parent.call( this, config );
   *     }
   *     OO.inheritClass( MyDialog, OO.ui.Dialog );
   *     MyDialog.static.name = 'myDialog';
   *     MyDialog.prototype.initialize = function () {
   *         MyDialog.parent.prototype.initialize.call( this );
   *         this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );
   *         this.content.$element.append( '<p>A simple dialog window. Press \'Esc\' to close.</p>' );
   *         this.$body.append( this.content.$element );
   *     };
   *     MyDialog.prototype.getBodyHeight = function () {
   *         return this.content.$element.outerHeight( true );
   *     };
   *     var myDialog = new MyDialog( {
   *         size: 'medium'
   *     } );
   *     // Create and append a window manager, which opens and closes the window.
   *     var windowManager = new OO.ui.WindowManager();
   *     $( 'body' ).append( windowManager.$element );
   *     windowManager.addWindows( [ myDialog ] );
   *     // Open the window!
   *     windowManager.openWindow( myDialog );
   *
   * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Dialogs
   *
   * @abstract
   * @class
   * @extends OO.ui.Window
   * @mixins OO.ui.mixin.PendingElement
   *
   * @constructor
   * @param {Object} [config] Configuration options
   */
  OO.ui.Dialog = function OoUiDialog( config ) {
  	// Parent constructor
  	OO.ui.Dialog.parent.call( this, config );
  
  	// Mixin constructors
  	OO.ui.mixin.PendingElement.call( this );
  
  	// Properties
  	this.actions = new OO.ui.ActionSet();
  	this.attachedActions = [];
  	this.currentAction = null;
  	this.onDialogKeyDownHandler = this.onDialogKeyDown.bind( this );
  
  	// Events
  	this.actions.connect( this, {
  		click: 'onActionClick',
  		change: 'onActionsChange'
  	} );
  
  	// Initialization
  	this.$element
  		.addClass( 'oo-ui-dialog' )
  		.attr( 'role', 'dialog' );
  };
  
  /* Setup */
  
  OO.inheritClass( OO.ui.Dialog, OO.ui.Window );
  OO.mixinClass( OO.ui.Dialog, OO.ui.mixin.PendingElement );
  
  /* Static Properties */
  
  /**
   * Symbolic name of dialog.
   *
   * The dialog class must have a symbolic name in order to be registered with OO.Factory.
   * Please see the [OOjs UI documentation on MediaWiki] [3] for more information.
   *
   * [3]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
   *
   * @abstract
   * @static
   * @inheritable
   * @property {string}
   */
  OO.ui.Dialog.static.name = '';
  
  /**
   * The dialog title.
   *
   * The title can be specified as a plaintext string, a {@link OO.ui.mixin.LabelElement Label} node, or a function
   * that will produce a Label node or string. The title can also be specified with data passed to the
   * constructor (see #getSetupProcess). In this case, the static value will be overridden.
   *
   * @abstract
   * @static
   * @inheritable
   * @property {jQuery|string|Function}
   */
  OO.ui.Dialog.static.title = '';
  
  /**
   * An array of configured {@link OO.ui.ActionWidget action widgets}.
   *
   * Actions can also be specified with data passed to the constructor (see #getSetupProcess). In this case, the static
   * value will be overridden.
   *
   * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Action_sets
   *
   * @static
   * @inheritable
   * @property {Object[]}
   */
  OO.ui.Dialog.static.actions = [];
  
  /**
   * Close the dialog when the 'Esc' key is pressed.
   *
   * @static
   * @abstract
   * @inheritable
   * @property {boolean}
   */
  OO.ui.Dialog.static.escapable = true;
  
  /* Methods */
  
  /**
   * Handle frame document key down events.
   *
   * @private
   * @param {jQuery.Event} e Key down event
   */
  OO.ui.Dialog.prototype.onDialogKeyDown = function ( e ) {
  	var actions;
  	if ( e.which === OO.ui.Keys.ESCAPE && this.constructor.static.escapable ) {
  		this.executeAction( '' );
  		e.preventDefault();
  		e.stopPropagation();
  	} else if ( e.which === OO.ui.Keys.ENTER && ( e.ctrlKey || e.metaKey ) ) {
  		actions = this.actions.get( { flags: 'primary', visible: true, disabled: false } );
  		if ( actions.length > 0 ) {
  			this.executeAction( actions[ 0 ].getAction() );
  			e.preventDefault();
  			e.stopPropagation();
  		}
  	}
  };
  
  /**
   * Handle action click events.
   *
   * @private
   * @param {OO.ui.ActionWidget} action Action that was clicked
   */
  OO.ui.Dialog.prototype.onActionClick = function ( action ) {
  	if ( !this.isPending() ) {
  		this.executeAction( action.getAction() );
  	}
  };
  
  /**
   * Handle actions change event.
   *
   * @private
   */
  OO.ui.Dialog.prototype.onActionsChange = function () {
  	this.detachActions();
  	if ( !this.isClosing() ) {
  		this.attachActions();
  	}
  };
  
  /**
   * Get the set of actions used by the dialog.
   *
   * @return {OO.ui.ActionSet}
   */
  OO.ui.Dialog.prototype.getActions = function () {
  	return this.actions;
  };
  
  /**
   * Get a process for taking action.
   *
   * When you override this method, you can create a new OO.ui.Process and return it, or add additional
   * accept steps to the process the parent method provides using the {@link OO.ui.Process#first 'first'}
   * and {@link OO.ui.Process#next 'next'} methods of OO.ui.Process.
   *
   * @param {string} [action] Symbolic name of action
   * @return {OO.ui.Process} Action process
   */
  OO.ui.Dialog.prototype.getActionProcess = function ( action ) {
  	return new OO.ui.Process()
  		.next( function () {
  			if ( !action ) {
  				// An empty action always closes the dialog without data, which should always be
  				// safe and make no changes
  				this.close();
  			}
  		}, this );
  };
  
  /**
   * @inheritdoc
   *
   * @param {Object} [data] Dialog opening data
   * @param {jQuery|string|Function|null} [data.title] Dialog title, omit to use
   *  the {@link #static-title static title}
   * @param {Object[]} [data.actions] List of configuration options for each
   *   {@link OO.ui.ActionWidget action widget}, omit to use {@link #static-actions static actions}.
   */
  OO.ui.Dialog.prototype.getSetupProcess = function ( data ) {
  	data = data || {};
  
  	// Parent method
  	return OO.ui.Dialog.parent.prototype.getSetupProcess.call( this, data )
  		.next( function () {
  			var config = this.constructor.static,
  				actions = data.actions !== undefined ? data.actions : config.actions,
  				title = data.title !== undefined ? data.title : config.title;
  
  			this.title.setLabel( title ).setTitle( title );
  			this.actions.add( this.getActionWidgets( actions ) );
  
  			this.$element.on( 'keydown', this.onDialogKeyDownHandler );
  		}, this );
  };
  
  /**
   * @inheritdoc
   */
  OO.ui.Dialog.prototype.getTeardownProcess = function ( data ) {
  	// Parent method
  	return OO.ui.Dialog.parent.prototype.getTeardownProcess.call( this, data )
  		.first( function () {
  			this.$element.off( 'keydown', this.onDialogKeyDownHandler );
  
  			this.actions.clear();
  			this.currentAction = null;
  		}, this );
  };
  
  /**
   * @inheritdoc
   */
  OO.ui.Dialog.prototype.initialize = function () {
  	// Parent method
  	OO.ui.Dialog.parent.prototype.initialize.call( this );
  
  	// Properties
  	this.title = new OO.ui.LabelWidget();
  
  	// Initialization
  	this.$content.addClass( 'oo-ui-dialog-content' );
  	this.$element.attr( 'aria-labelledby', this.title.getElementId() );
  	this.setPendingElement( this.$head );
  };
  
  /**
   * Get action widgets from a list of configs
   *
   * @param {Object[]} actions Action widget configs
   * @return {OO.ui.ActionWidget[]} Action widgets
   */
  OO.ui.Dialog.prototype.getActionWidgets = function ( actions ) {
  	var i, len, widgets = [];
  	for ( i = 0, len = actions.length; i < len; i++ ) {
  		widgets.push(
  			new OO.ui.ActionWidget( actions[ i ] )
  		);
  	}
  	return widgets;
  };
  
  /**
   * Attach action actions.
   *
   * @protected
   */
  OO.ui.Dialog.prototype.attachActions = function () {
  	// Remember the list of potentially attached actions
  	this.attachedActions = this.actions.get();
  };
  
  /**
   * Detach action actions.
   *
   * @protected
   * @chainable
   */
  OO.ui.Dialog.prototype.detachActions = function () {
  	var i, len;
  
  	// Detach all actions that may have been previously attached
  	for ( i = 0, len = this.attachedActions.length; i < len; i++ ) {
  		this.attachedActions[ i ].$element.detach();
  	}
  	this.attachedActions = [];
  };
  
  /**
   * Execute an action.
   *
   * @param {string} action Symbolic name of action to execute
   * @return {jQuery.Promise} Promise resolved when action completes, rejected if it fails
   */
  OO.ui.Dialog.prototype.executeAction = function ( action ) {
  	this.pushPending();
  	this.currentAction = action;
  	return this.getActionProcess( action ).execute()
  		.always( this.popPending.bind( this ) );
  };
  
  /**
   * MessageDialogs display a confirmation or alert message. By default, the rendered dialog box
   * consists of a header that contains the dialog title, a body with the message, and a footer that
   * contains any {@link OO.ui.ActionWidget action widgets}. The MessageDialog class is the only type
   * of {@link OO.ui.Dialog dialog} that is usually instantiated directly.
   *
   * There are two basic types of message dialogs, confirmation and alert:
   *
   * - **confirmation**: the dialog title describes what a progressive action will do and the message provides
   *  more details about the consequences.
   * - **alert**: the dialog title describes which event occurred and the message provides more information
   *  about why the event occurred.
   *
   * The MessageDialog class specifies two actions: ‘accept’, the primary
   * action (e.g., ‘ok’) and ‘reject,’ the safe action (e.g., ‘cancel’). Both will close the window,
   * passing along the selected action.
   *
   * For more information and examples, please see the [OOjs UI documentation on MediaWiki][1].
   *
   *     @example
   *     // Example: Creating and opening a message dialog window.
   *     var messageDialog = new OO.ui.MessageDialog();
   *
   *     // Create and append a window manager.
   *     var windowManager = new OO.ui.WindowManager();
   *     $( 'body' ).append( windowManager.$element );
   *     windowManager.addWindows( [ messageDialog ] );
   *     // Open the window.
   *     windowManager.openWindow( messageDialog, {
   *         title: 'Basic message dialog',
   *         message: 'This is the message'
   *     } );
   *
   * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Message_Dialogs
   *
   * @class
   * @extends OO.ui.Dialog
   *
   * @constructor
   * @param {Object} [config] Configuration options
   */
  OO.ui.MessageDialog = function OoUiMessageDialog( config ) {
  	// Parent constructor
  	OO.ui.MessageDialog.parent.call( this, config );
  
  	// Properties
  	this.verticalActionLayout = null;
  
  	// Initialization
  	this.$element.addClass( 'oo-ui-messageDialog' );
  };
  
  /* Setup */
  
  OO.inheritClass( OO.ui.MessageDialog, OO.ui.Dialog );
  
  /* Static Properties */
  
  /**
   * @static
   * @inheritdoc
   */
  OO.ui.MessageDialog.static.name = 'message';
  
  /**
   * @static
   * @inheritdoc
   */
  OO.ui.MessageDialog.static.size = 'small';
  
  /**
   * Dialog title.
   *
   * The title of a confirmation dialog describes what a progressive action will do. The
   * title of an alert dialog describes which event occurred.
   *
   * @static
   * @inheritable
   * @property {jQuery|string|Function|null}
   */
  OO.ui.MessageDialog.static.title = null;
  
  /**
   * The message displayed in the dialog body.
   *
   * A confirmation message describes the consequences of a progressive action. An alert
   * message describes why an event occurred.
   *
   * @static
   * @inheritable
   * @property {jQuery|string|Function|null}
   */
  OO.ui.MessageDialog.static.message = null;
  
  /**
   * @static
   * @inheritdoc
   */
  OO.ui.MessageDialog.static.actions = [
  	// Note that OO.ui.alert() and OO.ui.confirm() rely on these.
  	{ action: 'accept', label: OO.ui.deferMsg( 'ooui-dialog-message-accept' ), flags: 'primary' },
  	{ action: 'reject', label: OO.ui.deferMsg( 'ooui-dialog-message-reject' ), flags: 'safe' }
  ];
  
  /* Methods */
  
  /**
   * @inheritdoc
   */
  OO.ui.MessageDialog.prototype.setManager = function ( manager ) {
  	OO.ui.MessageDialog.parent.prototype.setManager.call( this, manager );
  
  	// Events
  	this.manager.connect( this, {
  		resize: 'onResize'
  	} );
  
  	return this;
  };
  
  /**
   * Handle window resized events.
   *
   * @private
   */
  OO.ui.MessageDialog.prototype.onResize = function () {
  	var dialog = this;
  	dialog.fitActions();
  	// Wait for CSS transition to finish and do it again :(
  	setTimeout( function () {
  		dialog.fitActions();
  	}, 300 );
  };
  
  /**
   * Toggle action layout between vertical and horizontal.
   *
   * @private
   * @param {boolean} [value] Layout actions vertically, omit to toggle
   * @chainable
   */
  OO.ui.MessageDialog.prototype.toggleVerticalActionLayout = function ( value ) {
  	value = value === undefined ? !this.verticalActionLayout : !!value;
  
  	if ( value !== this.verticalActionLayout ) {
  		this.verticalActionLayout = value;
  		this.$actions
  			.toggleClass( 'oo-ui-messageDialog-actions-vertical', value )
  			.toggleClass( 'oo-ui-messageDialog-actions-horizontal', !value );
  	}
  
  	return this;
  };
  
  /**
   * @inheritdoc
   */
  OO.ui.MessageDialog.prototype.getActionProcess = function ( action ) {
  	if ( action ) {
  		return new OO.ui.Process( function () {
  			this.close( { action: action } );
  		}, this );
  	}
  	return OO.ui.MessageDialog.parent.prototype.getActionProcess.call( this, action );
  };
  
  /**
   * @inheritdoc
   *
   * @param {Object} [data] Dialog opening data
   * @param {jQuery|string|Function|null} [data.title] Description of the action being confirmed
   * @param {jQuery|string|Function|null} [data.message] Description of the action's consequence
   * @param {string} [data.size] Symbolic name of the dialog size, see OO.ui.Window
   * @param {Object[]} [data.actions] List of OO.ui.ActionOptionWidget configuration options for each
   *   action item
   */
  OO.ui.MessageDialog.prototype.getSetupProcess = function ( data ) {
  	data = data || {};
  
  	// Parent method
  	return OO.ui.MessageDialog.parent.prototype.getSetupProcess.call( this, data )
  		.next( function () {
  			this.title.setLabel(
  				data.title !== undefined ? data.title : this.constructor.static.title
  			);
  			this.message.setLabel(
  				data.message !== undefined ? data.message : this.constructor.static.message
  			);
  			this.size = data.size !== undefined ? data.size : this.constructor.static.size;
  		}, this );
  };
  
  /**
   * @inheritdoc
   */
  OO.ui.MessageDialog.prototype.getReadyProcess = function ( data ) {
  	data = data || {};
  
  	// Parent method
  	return OO.ui.MessageDialog.parent.prototype.getReadyProcess.call( this, data )
  		.next( function () {
  			// Focus the primary action button
  			var actions = this.actions.get();
  			actions = actions.filter( function ( action ) {
  				return action.getFlags().indexOf( 'primary' ) > -1;
  			} );
  			if ( actions.length > 0 ) {
  				actions[ 0 ].focus();
  			}
  		}, this );
  };
  
  /**
   * @inheritdoc
   */
  OO.ui.MessageDialog.prototype.getBodyHeight = function () {
  	var bodyHeight, oldOverflow,
  		$scrollable = this.container.$element;
  
  	oldOverflow = $scrollable[ 0 ].style.overflow;
  	$scrollable[ 0 ].style.overflow = 'hidden';
  
  	OO.ui.Element.static.reconsiderScrollbars( $scrollable[ 0 ] );
  
  	bodyHeight = this.text.$element.outerHeight( true );
  	$scrollable[ 0 ].style.overflow = oldOverflow;
  
  	return bodyHeight;
  };
  
  /**
   * @inheritdoc
   */
  OO.ui.MessageDialog.prototype.setDimensions = function ( dim ) {
  	var $scrollable = this.container.$element;
  	OO.ui.MessageDialog.parent.prototype.setDimensions.call( this, dim );
  
  	// Twiddle the overflow property, otherwise an unnecessary scrollbar will be produced.
  	// Need to do it after transition completes (250ms), add 50ms just in case.
  	setTimeout( function () {
  		var oldOverflow = $scrollable[ 0 ].style.overflow,
  			activeElement = document.activeElement;
  
  		$scrollable[ 0 ].style.overflow = 'hidden';
  
  		OO.ui.Element.static.reconsiderScrollbars( $scrollable[ 0 ] );
  
  		// Check reconsiderScrollbars didn't destroy our focus, as we
  		// are doing this after the ready process.
  		if ( activeElement && activeElement !== document.activeElement && activeElement.focus ) {
  			activeElement.focus();
  		}
  
  		$scrollable[ 0 ].style.overflow = oldOverflow;
  	}, 300 );
  
  	return this;
  };
  
  /**
   * @inheritdoc
   */
  OO.ui.MessageDialog.prototype.initialize = function () {
  	// Parent method
  	OO.ui.MessageDialog.parent.prototype.initialize.call( this );
  
  	// Properties
  	this.$actions = $( '<div>' );
  	this.container = new OO.ui.PanelLayout( {
  		scrollable: true, classes: [ 'oo-ui-messageDialog-container' ]
  	} );
  	this.text = new OO.ui.PanelLayout( {
  		padded: true, expanded: false, classes: [ 'oo-ui-messageDialog-text' ]
  	} );
  	this.message = new OO.ui.LabelWidget( {
  		classes: [ 'oo-ui-messageDialog-message' ]
  	} );
  
  	// Initialization
  	this.title.$element.addClass( 'oo-ui-messageDialog-title' );
  	this.$content.addClass( 'oo-ui-messageDialog-content' );
  	this.container.$element.append( this.text.$element );
  	this.text.$element.append( this.title.$element, this.message.$element );
  	this.$body.append( this.container.$element );
  	this.$actions.addClass( 'oo-ui-messageDialog-actions' );
  	this.$foot.append( this.$actions );
  };
  
  /**
   * @inheritdoc
   */
  OO.ui.MessageDialog.prototype.attachActions = function () {
  	var i, len, other, special, others;
  
  	// Parent method
  	OO.ui.MessageDialog.parent.prototype.attachActions.call( this );
  
  	special = this.actions.getSpecial();
  	others = this.actions.getOthers();
  
  	if ( special.safe ) {
  		this.$actions.append( special.safe.$element );
  		special.safe.toggleFramed( false );
  	}
  	if ( others.length ) {
  		for ( i = 0, len = others.length; i < len; i++ ) {
  			other = others[ i ];
  			this.$actions.append( other.$element );
  			other.toggleFramed( false );
  		}
  	}
  	if ( special.primary ) {
  		this.$actions.append( special.primary.$element );
  		special.primary.toggleFramed( false );
  	}
  
  	if ( !this.isOpening() ) {
  		// If the dialog is currently opening, this will be called automatically soon.
  		// This also calls #fitActions.
  		this.updateSize();
  	}
  };
  
  /**
   * Fit action actions into columns or rows.
   *
   * Columns will be used if all labels can fit without overflow, otherwise rows will be used.
   *
   * @private
   */
  OO.ui.MessageDialog.prototype.fitActions = function () {
  	var i, len, action,
  		previous = this.verticalActionLayout,
  		actions = this.actions.get();
  
  	// Detect clipping
  	this.toggleVerticalActionLayout( false );
  	for ( i = 0, len = actions.length; i < len; i++ ) {
  		action = actions[ i ];
  		if ( action.$element.innerWidth() < action.$label.outerWidth( true ) ) {
  			this.toggleVerticalActionLayout( true );
  			break;
  		}
  	}
  
  	// Move the body out of the way of the foot
  	this.$body.css( 'bottom', this.$foot.outerHeight( true ) );
  
  	if ( this.verticalActionLayout !== previous ) {
  		// We changed the layout, window height might need to be updated.
  		this.updateSize();
  	}
  };
  
  /**
   * ProcessDialog windows encapsulate a {@link OO.ui.Process process} and all of the code necessary
   * to complete it. If the process terminates with an error, a customizable {@link OO.ui.Error error
   * interface} alerts users to the trouble, permitting the user to dismiss the error and try again when
   * relevant. The ProcessDialog class is always extended and customized with the actions and content
   * required for each process.
   *
   * The process dialog box consists of a header that visually represents the ‘working’ state of long
   * processes with an animation. The header contains the dialog title as well as
   * two {@link OO.ui.ActionWidget action widgets}:  a ‘safe’ action on the left (e.g., ‘Cancel’) and
   * a ‘primary’ action on the right (e.g., ‘Done’).
   *
   * Like other windows, the process dialog is managed by a {@link OO.ui.WindowManager window manager}.
   * Please see the [OOjs UI documentation on MediaWiki][1] for more information and examples.
   *
   *     @example
   *     // Example: Creating and opening a process dialog window.
   *     function MyProcessDialog( config ) {
   *         MyProcessDialog.parent.call( this, config );
   *     }
   *     OO.inheritClass( MyProcessDialog, OO.ui.ProcessDialog );
   *
   *     MyProcessDialog.static.name = 'myProcessDialog';
   *     MyProcessDialog.static.title = 'Process dialog';
   *     MyProcessDialog.static.actions = [
   *         { action: 'save', label: 'Done', flags: 'primary' },
   *         { label: 'Cancel', flags: 'safe' }
   *     ];
   *
   *     MyProcessDialog.prototype.initialize = function () {
   *         MyProcessDialog.parent.prototype.initialize.apply( this, arguments );
   *         this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );
   *         this.content.$element.append( '<p>This is a process dialog window. The header contains the title and two buttons: \'Cancel\' (a safe action) on the left and \'Done\' (a primary action)  on the right.</p>' );
   *         this.$body.append( this.content.$element );
   *     };
   *     MyProcessDialog.prototype.getActionProcess = function ( action ) {
   *         var dialog = this;
   *         if ( action ) {
   *             return new OO.ui.Process( function () {
   *                 dialog.close( { action: action } );
   *             } );
   *         }
   *         return MyProcessDialog.parent.prototype.getActionProcess.call( this, action );
   *     };
   *
   *     var windowManager = new OO.ui.WindowManager();
   *     $( 'body' ).append( windowManager.$element );
   *
   *     var dialog = new MyProcessDialog();
   *     windowManager.addWindows( [ dialog ] );
   *     windowManager.openWindow( dialog );
   *
   * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs
   *
   * @abstract
   * @class
   * @extends OO.ui.Dialog
   *
   * @constructor
   * @param {Object} [config] Configuration options
   */
  OO.ui.ProcessDialog = function OoUiProcessDialog( config ) {
  	// Parent constructor
  	OO.ui.ProcessDialog.parent.call( this, config );
  
  	// Properties
  	this.fitOnOpen = false;
  
  	// Initialization
  	this.$element.addClass( 'oo-ui-processDialog' );
  };
  
  /* Setup */
  
  OO.inheritClass( OO.ui.ProcessDialog, OO.ui.Dialog );
  
  /* Methods */
  
  /**
   * Handle dismiss button click events.
   *
   * Hides errors.
   *
   * @private
   */
  OO.ui.ProcessDialog.prototype.onDismissErrorButtonClick = function () {
  	this.hideErrors();
  };
  
  /**
   * Handle retry button click events.
   *
   * Hides errors and then tries again.
   *
   * @private
   */
  OO.ui.ProcessDialog.prototype.onRetryButtonClick = function () {
  	this.hideErrors();
  	this.executeAction( this.currentAction );
  };
  
  /**
   * @inheritdoc
   */
  OO.ui.ProcessDialog.prototype.initialize = function () {
  	// Parent method
  	OO.ui.ProcessDialog.parent.prototype.initialize.call( this );
  
  	// Properties
  	this.$navigation = $( '<div>' );
  	this.$location = $( '<div>' );
  	this.$safeActions = $( '<div>' );
  	this.$primaryActions = $( '<div>' );
  	this.$otherActions = $( '<div>' );
  	this.dismissButton = new OO.ui.ButtonWidget( {
  		label: OO.ui.msg( 'ooui-dialog-process-dismiss' )
  	} );
  	this.retryButton = new OO.ui.ButtonWidget();
  	this.$errors = $( '<div>' );
  	this.$errorsTitle = $( '<div>' );
  
  	// Events
  	this.dismissButton.connect( this, { click: 'onDismissErrorButtonClick' } );
  	this.retryButton.connect( this, { click: 'onRetryButtonClick' } );
  
  	// Initialization
  	this.title.$element.addClass( 'oo-ui-processDialog-title' );
  	this.$location
  		.append( this.title.$element )
  		.addClass( 'oo-ui-processDialog-location' );
  	this.$safeActions.addClass( 'oo-ui-processDialog-actions-safe' );
  	this.$primaryActions.addClass( 'oo-ui-processDialog-actions-primary' );
  	this.$otherActions.addClass( 'oo-ui-processDialog-actions-other' );
  	this.$errorsTitle
  		.addClass( 'oo-ui-processDialog-errors-title' )
  		.text( OO.ui.msg( 'ooui-dialog-process-error' ) );
  	this.$errors
  		.addClass( 'oo-ui-processDialog-errors oo-ui-element-hidden' )
  		.append( this.$errorsTitle, this.dismissButton.$element, this.retryButton.$element );
  	this.$content
  		.addClass( 'oo-ui-processDialog-content' )
  		.append( this.$errors );
  	this.$navigation
  		.addClass( 'oo-ui-processDialog-navigation' )
  		// Note: Order of appends below is important. These are in the order
  		// we want tab to go through them. Display-order is handled entirely
  		// by CSS absolute-positioning. As such, primary actions like "done"
  		// should go first.
  		.append( this.$primaryActions, this.$location, this.$safeActions );
  	this.$head.append( this.$navigation );
  	this.$foot.append( this.$otherActions );
  };
  
  /**
   * @inheritdoc
   */
  OO.ui.ProcessDialog.prototype.getActionWidgets = function ( actions ) {
  	var i, len, config,
  		isMobile = OO.ui.isMobile(),
  		widgets = [];
  
  	for ( i = 0, len = actions.length; i < len; i++ ) {
  		config = $.extend( { framed: !OO.ui.isMobile() }, actions[ i ] );
  		if ( isMobile &&
  			( config.flags === 'back' || ( Array.isArray( config.flags ) && config.flags.indexOf( 'back' ) !== -1 ) )
  		) {
  			$.extend( config, {
  				icon: 'previous',
  				label: ''
  			} );
  		}
  		widgets.push(
  			new OO.ui.ActionWidget( config )
  		);
  	}
  	return widgets;
  };
  
  /**
   * @inheritdoc
   */
  OO.ui.ProcessDialog.prototype.attachActions = function () {
  	var i, len, other, special, others;
  
  	// Parent method
  	OO.ui.ProcessDialog.parent.prototype.attachActions.call( this );
  
  	special = this.actions.getSpecial();
  	others = this.actions.getOthers();
  	if ( special.primary ) {
  		this.$primaryActions.append( special.primary.$element );
  	}
  	for ( i = 0, len = others.length; i < len; i++ ) {
  		other = others[ i ];
  		this.$otherActions.append( other.$element );
  	}
  	if ( special.safe ) {
  		this.$safeActions.append( special.safe.$element );
  	}
  
  	this.fitLabel();
  	this.$body.css( 'bottom', this.$foot.outerHeight( true ) );
  };
  
  /**
   * @inheritdoc
   */
  OO.ui.ProcessDialog.prototype.executeAction = function ( action ) {
  	var process = this;
  	return OO.ui.ProcessDialog.parent.prototype.executeAction.call( this, action )
  		.fail( function ( errors ) {
  			process.showErrors( errors || [] );
  		} );
  };
  
  /**
   * @inheritdoc
   */
  OO.ui.ProcessDialog.prototype.setDimensions = function () {
  	// Parent method
  	OO.ui.ProcessDialog.parent.prototype.setDimensions.apply( this, arguments );
  
  	this.fitLabel();
  };
  
  /**
   * Fit label between actions.
   *
   * @private
   * @chainable
   */
  OO.ui.ProcessDialog.prototype.fitLabel = function () {
  	var safeWidth, primaryWidth, biggerWidth, labelWidth, navigationWidth, leftWidth, rightWidth,
  		size = this.getSizeProperties();
  
  	if ( typeof size.width !== 'number' ) {
  		if ( this.isOpened() ) {
  			navigationWidth = this.$head.width() - 20;
  		} else if ( this.isOpening() ) {
  			if ( !this.fitOnOpen ) {
  				// Size is relative and the dialog isn't open yet, so wait.
  				// FIXME: This should ideally be handled by setup somehow.
  				this.manager.lifecycle.opened.done( this.fitLabel.bind( this ) );
  				this.fitOnOpen = true;
  			}
  			return;
  		} else {
  			return;
  		}
  	} else {
  		navigationWidth = size.width - 20;
  	}
  
  	safeWidth = this.$safeActions.is( ':visible' ) ? this.$safeActions.width() : 0;
  	primaryWidth = this.$primaryActions.is( ':visible' ) ? this.$primaryActions.width() : 0;
  	biggerWidth = Math.max( safeWidth, primaryWidth );
  
  	labelWidth = this.title.$element.width();
  
  	if ( 2 * biggerWidth + labelWidth < navigationWidth ) {
  		// We have enough space to center the label
  		leftWidth = rightWidth = biggerWidth;
  	} else {
  		// Let's hope we at least have enough space not to overlap, because we can't wrap the label…
  		if ( this.getDir() === 'ltr' ) {
  			leftWidth = safeWidth;
  			rightWidth = primaryWidth;
  		} else {
  			leftWidth = primaryWidth;
  			rightWidth = safeWidth;
  		}
  	}
  
  	this.$location.css( { paddingLeft: leftWidth, paddingRight: rightWidth } );
  
  	return this;
  };
  
  /**
   * Handle errors that occurred during accept or reject processes.
   *
   * @private
   * @param {OO.ui.Error[]|OO.ui.Error} errors Errors to be handled
   */
  OO.ui.ProcessDialog.prototype.showErrors = function ( errors ) {
  	var i, len, $item, actions,
  		items = [],
  		abilities = {},
  		recoverable = true,
  		warning = false;
  
  	if ( errors instanceof OO.ui.Error ) {
  		errors = [ errors ];
  	}
  
  	for ( i = 0, len = errors.length; i < len; i++ ) {
  		if ( !errors[ i ].isRecoverable() ) {
  			recoverable = false;
  		}
  		if ( errors[ i ].isWarning() ) {
  			warning = true;
  		}
  		$item = $( '<div>' )
  			.addClass( 'oo-ui-processDialog-error' )
  			.append( errors[ i ].getMessage() );
  		items.push( $item[ 0 ] );
  	}
  	this.$errorItems = $( items );
  	if ( recoverable ) {
  		abilities[ this.currentAction ] = true;
  		// Copy the flags from the first matching action
  		actions = this.actions.get( { actions: this.currentAction } );
  		if ( actions.length ) {
  			this.retryButton.clearFlags().setFlags( actions[ 0 ].getFlags() );
  		}
  	} else {
  		abilities[ this.currentAction ] = false;
  		this.actions.setAbilities( abilities );
  	}
  	if ( warning ) {
  		this.retryButton.setLabel( OO.ui.msg( 'ooui-dialog-process-continue' ) );
  	} else {
  		this.retryButton.setLabel( OO.ui.msg( 'ooui-dialog-process-retry' ) );
  	}
  	this.retryButton.toggle( recoverable );
  	this.$errorsTitle.after( this.$errorItems );
  	this.$errors.removeClass( 'oo-ui-element-hidden' ).scrollTop( 0 );
  };
  
  /**
   * Hide errors.
   *
   * @private
   */
  OO.ui.ProcessDialog.prototype.hideErrors = function () {
  	this.$errors.addClass( 'oo-ui-element-hidden' );
  	if ( this.$errorItems ) {
  		this.$errorItems.remove();
  		this.$errorItems = null;
  	}
  };
  
  /**
   * @inheritdoc
   */
  OO.ui.ProcessDialog.prototype.getTeardownProcess = function ( data ) {
  	// Parent method
  	return OO.ui.ProcessDialog.parent.prototype.getTeardownProcess.call( this, data )
  		.first( function () {
  			// Make sure to hide errors
  			this.hideErrors();
  			this.fitOnOpen = false;
  		}, this );
  };
  
  /**
   * @class OO.ui
   */
  
  /**
   * Lazy-initialize and return a global OO.ui.WindowManager instance, used by OO.ui.alert and
   * OO.ui.confirm.
   *
   * @private
   * @return {OO.ui.WindowManager}
   */
  OO.ui.getWindowManager = function () {
  	if ( !OO.ui.windowManager ) {
  		OO.ui.windowManager = new OO.ui.WindowManager();
  		$( 'body' ).append( OO.ui.windowManager.$element );
  		OO.ui.windowManager.addWindows( [ new OO.ui.MessageDialog() ] );
  	}
  	return OO.ui.windowManager;
  };
  
  /**
   * Display a quick modal alert dialog, using a OO.ui.MessageDialog. While the dialog is open, the
   * rest of the page will be dimmed out and the user won't be able to interact with it. The dialog
   * has only one action button, labelled "OK", clicking it will simply close the dialog.
   *
   * A window manager is created automatically when this function is called for the first time.
   *
   *     @example
   *     OO.ui.alert( 'Something happened!' ).done( function () {
   *         console.log( 'User closed the dialog.' );
   *     } );
   *
   *     OO.ui.alert( 'Something larger happened!', { size: 'large' } );
   *
   * @param {jQuery|string} text Message text to display
   * @param {Object} [options] Additional options, see OO.ui.MessageDialog#getSetupProcess
   * @return {jQuery.Promise} Promise resolved when the user closes the dialog
   */
  OO.ui.alert = function ( text, options ) {
  	return OO.ui.getWindowManager().openWindow( 'message', $.extend( {
  		message: text,
  		actions: [ OO.ui.MessageDialog.static.actions[ 0 ] ]
  	}, options ) ).closed.then( function () {
  		return undefined;
  	} );
  };
  
  /**
   * Display a quick modal confirmation dialog, using a OO.ui.MessageDialog. While the dialog is open,
   * the rest of the page will be dimmed out and the user won't be able to interact with it. The
   * dialog has two action buttons, one to confirm an operation (labelled "OK") and one to cancel it
   * (labelled "Cancel").
   *
   * A window manager is created automatically when this function is called for the first time.
   *
   *     @example
   *     OO.ui.confirm( 'Are you sure?' ).done( function ( confirmed ) {
   *         if ( confirmed ) {
   *             console.log( 'User clicked "OK"!' );
   *         } else {
   *             console.log( 'User clicked "Cancel" or closed the dialog.' );
   *         }
   *     } );
   *
   * @param {jQuery|string} text Message text to display
   * @param {Object} [options] Additional options, see OO.ui.MessageDialog#getSetupProcess
   * @return {jQuery.Promise} Promise resolved when the user closes the dialog. If the user chose to
   *  confirm, the promise will resolve to boolean `true`; otherwise, it will resolve to boolean
   *  `false`.
   */
  OO.ui.confirm = function ( text, options ) {
  	return OO.ui.getWindowManager().openWindow( 'message', $.extend( {
  		message: text
  	}, options ) ).closed.then( function ( data ) {
  		return !!( data && data.action === 'accept' );
  	} );
  };
  
  /**
   * Display a quick modal prompt dialog, using a OO.ui.MessageDialog. While the dialog is open,
   * the rest of the page will be dimmed out and the user won't be able to interact with it. The
   * dialog has a text input widget and two action buttons, one to confirm an operation (labelled "OK")
   * and one to cancel it (labelled "Cancel").
   *
   * A window manager is created automatically when this function is called for the first time.
   *
   *     @example
   *     OO.ui.prompt( 'Choose a line to go to', { textInput: { placeholder: 'Line number' } } ).done( function ( result ) {
   *         if ( result !== null ) {
   *             console.log( 'User typed "' + result + '" then clicked "OK".' );
   *         } else {
   *             console.log( 'User clicked "Cancel" or closed the dialog.' );
   *         }
   *     } );
   *
   * @param {jQuery|string} text Message text to display
   * @param {Object} [options] Additional options, see OO.ui.MessageDialog#getSetupProcess
   * @param {Object} [options.textInput] Additional options for text input widget, see OO.ui.TextInputWidget
   * @return {jQuery.Promise} Promise resolved when the user closes the dialog. If the user chose to
   *  confirm, the promise will resolve with the value of the text input widget; otherwise, it will
   *  resolve to `null`.
   */
  OO.ui.prompt = function ( text, options ) {
  	var instance,
  		manager = OO.ui.getWindowManager(),
  		textInput = new OO.ui.TextInputWidget( ( options && options.textInput ) || {} ),
  		textField = new OO.ui.FieldLayout( textInput, {
  			align: 'top',
  			label: text
  		} );
  
  	instance = manager.openWindow( 'message', $.extend( {
  		message: textField.$element
  	}, options ) );
  
  	// TODO: This is a little hacky, and could be done by extending MessageDialog instead.
  	instance.opened.then( function () {
  		textInput.on( 'enter', function () {
  			manager.getCurrentWindow().close( { action: 'accept' } );
  		} );
  		textInput.focus();
  	} );
  
  	return instance.closed.then( function ( data ) {
  		return data && data.action === 'accept' ? textInput.getValue() : null;
  	} );
  };
  
  }( OO ) );
  
  //# sourceMappingURL=oojs-ui-windows.js.map