diff --git a/core/includes/ajax.inc b/core/includes/ajax.inc
index 212dff6..31c099e 100644
--- a/core/includes/ajax.inc
+++ b/core/includes/ajax.inc
@@ -473,13 +473,8 @@ function ajax_prepare_response($page_callback_result) {
     // manipulation method is used. The method used is specified by
     // #ajax['method']. The default method is 'replaceWith', which completely
     // replaces the old wrapper element and its content with the new HTML.
-    // Since this is the primary response content returned to the client, we
-    // also attach the page title. It is up to client code to determine if and
-    // how to display that. For example, if the requesting element is configured
-    // to display the response in a dialog (via #ajax['dialog']), it can use
-    // this for the dialog title.
     $html = is_string($page_callback_result) ? $page_callback_result : drupal_render($page_callback_result);
-    $commands[] = ajax_command_insert(NULL, $html) + array('title' => drupal_get_title());
+    $commands[] = ajax_command_insert(NULL, $html);
     // Add the status messages inside the new content's wrapper element, so that
     // on subsequent Ajax requests, it is treated as old content.
     $commands[] = ajax_command_prepend(NULL, theme('status_messages'));
@@ -594,9 +589,6 @@ function ajax_pre_render_element($element) {
   if (isset($element['#ajax']['event'])) {
     $element['#attached']['library'][] = array('system', 'jquery.form');
     $element['#attached']['library'][] = array('system', 'drupal.ajax');
-    if (!empty($element['#ajax']['dialog'])) {
-      $element['#attached']['library'][] = array('system', 'drupal.dialog');
-    }
 
     $settings = $element['#ajax'];
 
diff --git a/core/lib/Drupal/Core/Ajax/CloseDialogCommand.php b/core/lib/Drupal/Core/Ajax/CloseDialogCommand.php
new file mode 100644
index 0000000..1788ceb
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/CloseDialogCommand.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Ajax\CloseDialogCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+/**
+ * Defines an AJAX command that closes the current active dialog.
+ */
+class CloseDialogCommand implements CommandInterface {
+
+  /**
+   * A CSS selector string of the dialog to close.
+   *
+   * @var string
+   */
+  protected $selector;
+
+  /**
+   * Constructs a CloseDialogCommand object.
+   *
+   * @param string $selector
+   *   A CSS selector string of the dialog to close.
+   */
+  public function __construct($selector = NULL) {
+    $this->selector = $selector ? $selector : '#drupal-modal';
+  }
+
+  /**
+   * Implements \Drupal\Core\Ajax\CommandInterface::render().
+   */
+  public function render() {
+    return array(
+      'command' => 'closeDialog',
+      'selector' => $this->selector,
+    );
+  }
+}
diff --git a/core/lib/Drupal/Core/Ajax/CloseModalDialogCommand.php b/core/lib/Drupal/Core/Ajax/CloseModalDialogCommand.php
new file mode 100644
index 0000000..85c4057
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/CloseModalDialogCommand.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Ajax\CloseDialogCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Ajax\CloseDialogCommand;
+
+/**
+ * Defines an AJAX command that closes the currently visible modal dialog.
+ */
+class CloseModalDialogCommand extends CloseDialogCommand {
+  /**
+   * Constructs a CloseDialogCommand object.
+   */
+  public function __construct() {
+    $this->selector = '#drupal-modal';
+  }
+}
diff --git a/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php b/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php
new file mode 100644
index 0000000..45a4b25
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Ajax\OpenDialogCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Ajax\CommandInterface;
+
+/**
+ * Defines an AJAX command to open certain content in a dialog.
+ */
+class OpenDialogCommand implements CommandInterface {
+
+  /**
+   * The selector of the dialog.
+   *
+   * @var string
+   */
+  protected $selector;
+
+  /**
+   * The title of the dialog.
+   *
+   * @var string
+   */
+  protected $title;
+
+  /**
+   * HTML content that will placed in the dialog.
+   *
+   * @var string
+   */
+  protected $html;
+
+  /**
+   * Stores dialog-specific options passed directly to jQuery UI dialogs.
+   *
+   * @var array
+   */
+  protected $dialogOptions;
+
+  /**
+   * Other settings that will be passed to the behavior.
+   *
+   * @var array
+   */
+  protected $settings;
+
+  /**
+   * Constructs an OpenDialogCommand object.
+   *
+   * @param string $selector
+   *   The selector of the dialog.
+   * @param string $title
+   *   The title of the dialog.
+   * @param string $html
+   *   HTML that will be placed in the dialog.
+   * @param array $dialog_options
+   *   (optional) Options to be passed to the dialog implementation. Any
+   *   jQuery UI option can be used. See http://api.jqueryui.com/dialog.
+   * @param array|null $settings
+   *   (optional) Other settings that will be passed to the behavior.
+   */
+  public function __construct($selector, $title, $html, array $dialog_options = array(), $settings = NULL) {
+    $dialog_options += array('title' => $title);
+    $this->selector = $selector;
+    $this->html = $html;
+    $this->dialogOptions = $dialog_options;
+    $this->settings = $settings;
+  }
+
+  /**
+   * Returns the dialog options.
+   *
+   * @return array
+   */
+  public function getdialogOptions() {
+    return $this->dialogOptions;
+  }
+
+  /**
+   * Sets the dialog options array.
+   *
+   * @param array $dialog_options
+   *   An array of keys passed to the Drupal.dialog javascript object.
+   */
+  public function setdialogOptions($dialog_options) {
+    $this->dialogOptions = $dialog_options;
+  }
+
+  /**
+   * Sets a single dialog option value.
+   *
+   * @param string $key
+   *   Key of the dialog option.
+   * @param mixed $value
+   *   The value of the option.
+   */
+  public function setDialogOption($key, $value) {
+    $this->dialogOptions[$key] = $value;
+  }
+
+  /**
+   * Sets the dialog title (an alias of setDialogOptions).
+   *
+   * @param mixed $title
+   *   The new title of the dialog.
+   */
+  public function setDialogTitle($title) {
+    $this->setDialogOptions('title', $title);
+  }
+
+  /**
+   * Implements \Drupal\Core\Ajax\CommandInterface:render().
+   */
+  public function render() {
+    // Add the library for handling the dialog in the response.
+    drupal_add_library('system', 'drupal.dialog.ajax');
+
+    // For consistency ensure the modal option is set to TRUE or FALSE.
+    $this->dialogOptions['modal'] = isset($this->dialogOptions['modal']) && $this->dialogOptions['modal'];
+    return array(
+      'command' => 'openDialog',
+      'selector' => $this->selector,
+      'settings' => $this->settings,
+      'data' => $this->html,
+      'dialogOptions' => $this->dialogOptions,
+    );
+  }
+}
diff --git a/core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php b/core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php
new file mode 100644
index 0000000..fe1afe7
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Ajax\OpenModalDialogCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Ajax\OpenDialogCommand;
+
+/**
+ * Defines an AJAX command to open certain content in a dialog in a modal dialog.
+ */
+class OpenModalDialogCommand extends OpenDialogCommand {
+  /**
+   * Constructs an OpenModalDialog object.
+   *
+   * The modal dialog differs from the normal modal provided by
+   * OpenDialogCommand in that a modal prevents other interactions on the page
+   * until the modal has been completed. Drupal provides a built-in modal for
+   * this purpose, so no selector needs to be provided.
+   *
+   * @param string $title
+   *   The title of the dialog.
+   * @param string $html
+   *   HTML that will be placed in the dialog.
+   * @param array $dialog_options
+   *   (optional) Settings to be passed to the dialog implementation. Any
+   *   jQuery UI option can be used. See http://api.jqueryui.com/dialog.
+   * @param array|null $settings
+   *   (optional) Other settings that will be passed to the behavior.
+   */
+  public function __construct($title, $html, array $dialog_options = array(), $settings = NULL) {
+    $dialog_options['modal'] = TRUE;
+    parent::__construct('#drupal-modal', $title, $html, $dialog_options, $settings);
+  }
+}
diff --git a/core/lib/Drupal/Core/Ajax/SetDialogOptionCommand.php b/core/lib/Drupal/Core/Ajax/SetDialogOptionCommand.php
new file mode 100644
index 0000000..b4f5454
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/SetDialogOptionCommand.php
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Ajax\SetDialogOptionCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+/**
+ * Defines an AJAX command that sets jQuery UI dialog properties.
+ */
+class SetDialogOptionCommand implements CommandInterface {
+
+  /**
+   * A CSS selector string.
+   *
+   * @var string
+   */
+  protected $selector;
+
+  /**
+   * A jQuery UI dialog option name.
+   *
+   * @var string
+   */
+  protected $option_name;
+
+  /**
+   * A jQuery UI dialog option value.
+   *
+   * @var mixed
+   */
+  protected $option_value;
+
+  /**
+   * Constructs a CloseDialogCommand object.
+   *
+   * @param string $selector
+   *   The selector of the dialog to close.
+   * @param string $option_name
+   *   The name of the option to set. May be any jQuery UI dialog option.
+   *   See See http://api.jqueryui.com/dialog.
+   * @param mixed $option_value
+   *   The value of the option to be passed to the dialog.
+   */
+  public function __construct($selector = NULL, $option_name, $option_value) {
+    $this->selector = $selector ? $selector : '#drupal-modal';
+    $this->option_name = $option_name;
+    $this->option_value = $option_value;
+  }
+
+  /**
+   * Implements \Drupal\Core\Ajax\CommandInterface::render().
+   */
+  public function render() {
+    return array(
+      'command' => 'setDialogOption',
+      'selector' => $this->selector,
+      'optionName' => $this->option_name,
+      'optionValue' => $this->option_value,
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Ajax/SetDialogTitleCommand.php b/core/lib/Drupal/Core/Ajax/SetDialogTitleCommand.php
new file mode 100644
index 0000000..d7aaf88
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/SetDialogTitleCommand.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Ajax\SetDialogTitleCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Ajax\SetDialogOptionCommand;
+
+/**
+ * Defines an AJAX command that sets jQuery UI dialog properties.
+ */
+class SetDialogTitleCommand extends SetDialogOptionCommand {
+
+  /**
+   * Constructs a SetDialogTitleCommand object.
+   *
+   * @param string $selector
+   *   The selector of the dialog whose title will be set.
+   * @param string $title
+   *   The title that will be set on the dialog.
+   */
+  public function __construct($selector = NULL, $title) {
+    $this->selector = $selector ? $selector : '#drupal-modal';
+    $this->option_name = 'title';
+    $this->option_value = $title;
+  }
+}
diff --git a/core/lib/Drupal/Core/Ajax/SetWindowLocationCommand.php b/core/lib/Drupal/Core/Ajax/SetWindowLocationCommand.php
new file mode 100644
index 0000000..a4ee25a
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/SetWindowLocationCommand.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Ajax\SetWindowLocationCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Ajax\CommandInterface;
+
+/**
+ * Defines an AJAX command to set the window.location, loading that URL.
+ */
+class SetWindowLocationCommand implements CommandInterface {
+
+  /**
+   * The URL that will be loaded into window.location.
+   *
+   * @var string
+   */
+  protected $url;
+
+  /**
+   * Constructs an OpenDialog object.
+   *
+   * @param string $url
+   *   The URL that will be loaded into window.location. This should be a full
+   *   URL, one that has already been run through the url() function.
+   */
+  public function __construct($url) {
+    $this->url = $url;
+  }
+
+  /**
+   * Implements \Drupal\Core\Ajax\CommandInterface:render().
+   */
+  public function render() {
+    return array(
+      'command' => 'setWindowLocation',
+      'url' => $this->url,
+    );
+  }
+
+}
diff --git a/core/misc/ajax.js b/core/misc/ajax.js
index 7ca4463..08303f0 100644
--- a/core/misc/ajax.js
+++ b/core/misc/ajax.js
@@ -126,15 +126,6 @@ Drupal.ajax = function (base, element, element_settings) {
     this.wrapper = '#' + this.wrapper;
   }
 
-  // For Ajax responses that are wanted in a dialog, use the needed method.
-  // If wanted in a modal dialog, also use the needed wrapper.
-  if (this.dialog) {
-    this.method = 'html';
-    if (this.dialog.modal) {
-      this.wrapper = '#drupal-modal';
-    }
-  }
-
   this.element = element;
   this.element_settings = element_settings;
 
@@ -561,19 +552,6 @@ Drupal.ajax.prototype.commands = {
     // Add the new content to the page.
     wrapper[method](new_content);
 
-    // If the requesting object wanted the response in a dialog, open that
-    // dialog. However, a single server response can include multiple insert
-    // commands (e.g., one for the primary content and another one for status
-    // messages), but we only want to open the dialog once, so we assume that
-    // only commands with a title property are dialog eligible.
-    // @todo Consider whether this is overloading title inappropriately, and
-    //   if so, find another way to determine dialog eligibility.
-    if (ajax.dialog && ('title' in response)) {
-      var dialogOptions = $.extend({title: response.title}, ajax.dialog);
-      var dialog = Drupal.dialog(wrapper, dialogOptions);
-      ajax.dialog.modal ? dialog.showModal() : dialog.show();
-    }
-
     // Immediately hide the new content if we're using any effects.
     if (effect.showEffect !== 'show') {
       new_content.hide();
@@ -629,6 +607,13 @@ Drupal.ajax.prototype.commands = {
   },
 
   /**
+   * Command to set the window.location.
+   */
+  setWindowLocation: function (ajax, response, status) {
+    window.location = response.url;
+  },
+
+  /**
    * Command to provide the jQuery css() function.
    */
   css: function (ajax, response, status) {
diff --git a/core/misc/dialog.ajax.js b/core/misc/dialog.ajax.js
new file mode 100644
index 0000000..d76baf8
--- /dev/null
+++ b/core/misc/dialog.ajax.js
@@ -0,0 +1,63 @@
+/**
+ * @file
+ * Extends the Drupal AJAX functionality to integrate the dialog API.
+ */
+
+(function ($, Drupal) {
+  "use strict";
+
+  /**
+   * Command to open a dialog.
+   */
+  Drupal.ajax.prototype.commands.openDialog = function (ajax, response, status) {
+    if (!response.selector) {
+      return false;
+    }
+    var $dialog = $(response.selector);
+    if (!$dialog.length) {
+      // Create the element if needed.
+      $dialog = $('<div id="' + response.selector.replace(/^#/, '') + '"/>').appendTo('body');
+    }
+    // Set up the wrapper, if there isn't one.
+    if (!ajax.wrapper) {
+      ajax.wrapper = $dialog.attr('id');
+    }
+
+    // Open the dialog itself.
+    response.dialogOptions = response.dialogOptions || {};
+    var dialog = new Drupal.dialog($dialog, response.dialogOptions);
+    dialog.open();
+
+    // Use the ajax.js insert command to populate the dialog contents.
+    response.command = 'insert';
+    response.method = 'html';
+    ajax.commands.insert(ajax, response, status);
+  };
+
+  /**
+   * Command to close a dialog.
+   *
+   * If no selector is given, it defaults to trying to close the modal.
+   */
+  Drupal.ajax.prototype.commands.closeDialog = function (ajax, response, status) {
+    var $dialog = $(response.selector);
+    if ($dialog.length) {
+      var dialog = new Drupal.dialog($dialog);
+      dialog.close();
+    }
+  };
+
+  /**
+   * Command to set a dialog property.
+   *
+   * If no selector is given, it defaults to setting the modal properties.
+   */
+  Drupal.ajax.prototype.commands.setDialogOption = function (ajax, response, status) {
+    var $dialog = $(response.selector);
+    if ($dialog.length) {
+      var dialog = new Drupal.dialog($dialog);
+      dialog.element.dialog('option', response.optionName, response.optionValue);
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/misc/dialog.js b/core/misc/dialog.js
index a0849bc..1ddc93c 100644
--- a/core/misc/dialog.js
+++ b/core/misc/dialog.js
@@ -10,10 +10,7 @@
 
 drupalSettings.dialog = {
   autoOpen: true,
-  dialogClass: '',
-  close: function (e) {
-    Drupal.detachBehaviors(e.target, null, 'unload');
-  }
+  dialogClass: ''
 };
 
 Drupal.behaviors.dialog = {
@@ -28,46 +25,38 @@ Drupal.behaviors.dialog = {
 };
 
 Drupal.dialog = function (element, options) {
+  // Set a default for options, making it optional.
+  options = options ? options : {};
 
-  function openDialog (settings) {
-    settings = $.extend({}, drupalSettings.dialog, options, settings);
-    // Trigger a global event to allow scripts to bind events to the dialog.
-    $(window).trigger('dialog:beforecreate', [dialog, $element, settings]);
-    $element.dialog(settings);
-    dialog.open = true;
-    $(window).trigger('dialog:aftercreate', [dialog, $element, settings]);
-  }
-
-  function closeDialog (value) {
-    $(window).trigger('dialog:beforeclose', [dialog, $element]);
-    $element.dialog('close');
-    dialog.returnValue = value;
-    dialog.open = false;
-    $(window).trigger('dialog:afterclose', [dialog, $element]);
-  }
+  this.element = $(element);
+  this.isOpen = false;
+  this.returnValue = undefined;
+  this.options = $.extend({}, drupalSettings.dialog, options);
+};
 
-  var undef;
-  var $element = $(element);
-  var dialog = {
-    open: false,
-    returnValue: undef,
-    show: function () {
-      openDialog({modal: false});
-    },
-    showModal: function () {
-      openDialog({modal: true});
-    },
-    close: closeDialog
-  };
+Drupal.dialog.prototype.open = function (options) {
+  options = $.extend({}, drupalSettings.dialog, this.options, options);
+  // Trigger a global event to allow scripts to bind events to the dialog.
+  $(window).trigger('dialog:beforecreate', [this, options]);
+  this.element.dialog(options);
+  this.isOpen = true;
+  $(window).trigger('dialog:aftercreate', [this, options]);
+}
 
-  return dialog;
-};
+Drupal.dialog.prototype.close = function (value) {
+  $(window).trigger('dialog:beforeclose', [this]);
+  Drupal.detachBehaviors(this.element[0], null, 'unload');
+  this.element.dialog('close');
+  this.returnValue = value;
+  this.isOpen = false;
+  $(window).trigger('dialog:afterclose', [this]);
+}
 
 /**
  * Binds a listener on dialog creation to handle the cancel link.
  */
-$(window).on('dialog:aftercreate', function (e, dialog, $element, settings) {
-  $element.on('click.dialog', '.dialog-cancel', function (e) {
+$(window).on('dialog:aftercreate', function (e, dialog, settings) {
+  dialog.element.on('click.dialog', '.dialog-cancel', function (e) {
     dialog.close('cancel');
     e.preventDefault();
     e.stopPropagation();
@@ -77,8 +66,8 @@ $(window).on('dialog:aftercreate', function (e, dialog, $element, settings) {
 /**
  * Removes all 'dialog' listeners.
  */
-$(window).on('dialog:beforeclose', function (e, dialog, $element) {
-  $element.off('.dialog');
+$(window).on('dialog:beforeclose', function (e, dialog) {
+  dialog.element.off('.dialog');
 });
 
 })(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/system/lib/Drupal/system/Tests/Ajax/AjaxCommandsUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Ajax/AjaxCommandsUnitTest.php
index 2e6c421..6b1a831 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Ajax/AjaxCommandsUnitTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Ajax/AjaxCommandsUnitTest.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\system\Tests\Ajax;
 
-use Drupal\simpletest\UnitTestBase;
+use Drupal\simpletest\DrupalUnitTestBase;
 use Drupal\Core\Ajax\AddCssCommand;
 use Drupal\Core\Ajax\AfterCommand;
 use Drupal\Core\Ajax\AlertCommand;
@@ -24,11 +24,18 @@
 use Drupal\Core\Ajax\ReplaceCommand;
 use Drupal\Core\Ajax\RestripeCommand;
 use Drupal\Core\Ajax\SettingsCommand;
+use Drupal\Core\Ajax\OpenDialogCommand;
+use Drupal\Core\Ajax\OpenModalDialogCommand;
+use Drupal\Core\Ajax\CloseDialogCommand;
+use Drupal\Core\Ajax\CloseModalDialogCommand;
+use Drupal\Core\Ajax\SetDialogOptionCommand;
+use Drupal\Core\Ajax\SetDialogTitleCommand;
+use Drupal\Core\Ajax\SetWindowLocationCommand;
 
 /**
  * Tests for all AJAX Commands.
  */
-class AjaxCommandsUnitTest extends UnitTestBase {
+class AjaxCommandsUnitTest extends DrupalUnitTestBase {
 
   public static function getInfo() {
     return array(
@@ -305,5 +312,121 @@ function testSettingsCommand() {
     $this->assertEqual($command->render(), $expected, 'SettingsCommand::render() returns a proper array.');
   }
 
-}
+  /**
+   * Tests that OpenDialogCommand objects can be constructed and rendered.
+   */
+  function testOpenDialogCommand() {
+    $command = new OpenDialogCommand('#some-dialog', 'Title', '<p>Text!</p>', array(
+      'url' => FALSE,
+      'width' => 500,
+    ));
+
+    $expected = array(
+      'command' => 'openDialog',
+      'selector' => '#some-dialog',
+      'settings' => NULL,
+      'data' => '<p>Text!</p>',
+      'dialogOptions' => array(
+        'url' => FALSE,
+        'width' => 500,
+        'title' => 'Title',
+        'modal' => FALSE,
+      ),
+    );
+    $this->assertEqual($command->render(), $expected, 'OpenDialogCommand::render() returns a proper array.');
+  }
+
+  /**
+   * Tests that OpenModalDialogCommand objects can be constructed and rendered.
+   */
+  function testOpenModalDialogCommand() {
+    $command = new OpenModalDialogCommand('Title', '<p>Text!</p>', array(
+      'url' => 'example',
+      'width' => 500,
+    ));
+
+    $expected = array(
+      'command' => 'openDialog',
+      'selector' => '#drupal-modal',
+      'settings' => NULL,
+      'data' => '<p>Text!</p>',
+      'dialogOptions' => array(
+        'url' => 'example',
+        'width' => 500,
+        'title' => 'Title',
+        'modal' => TRUE,
+      ),
+    );
+    $this->assertEqual($command->render(), $expected, 'OpenModalDialogCommand::render() returns a proper array.');
+  }
+
+  /**
+   * Tests that CloseModalDialogCommand objects can be constructed and rendered.
+   */
+  function testCloseModalDialogCommand() {
+    $command = new CloseModalDialogCommand();
+    $expected = array(
+      'command' => 'closeDialog',
+      'selector' => '#drupal-modal',
+    );
+
+    $this->assertEqual($command->render(), $expected, 'CloseModalDialogCommand::render() returns a proper array.');
+  }
+
+  /**
+   * Tests that CloseDialogCommand objects can be constructed and rendered.
+   */
+  function testCloseDialogCommand() {
+    $command = new CloseDialogCommand('#some-dialog');
+    $expected = array(
+      'command' => 'closeDialog',
+      'selector' => '#some-dialog',
+    );
+
+    $this->assertEqual($command->render(), $expected, 'CloseDialogCommand::render() with a selector returns a proper array.');
+  }
+
+  /**
+   * Tests that SetDialogOptionCommand objects can be constructed and rendered.
+   */
+  function testSetDialogOptionCommand() {
+    $command = new SetDialogOptionCommand('#some-dialog', 'width', '500');
+    $expected = array(
+      'command' => 'setDialogOption',
+      'selector' => '#some-dialog',
+      'optionName' => 'width',
+      'optionValue' => '500',
+    );
+
+    $this->assertEqual($command->render(), $expected, 'SetDialogOptionCommand::render() with a selector returns a proper array.');
+  }
+
+  /**
+   * Tests that SetDialogTitleCommand objects can be constructed and rendered.
+   */
+  function testSetDialogTitleCommand() {
+    $command = new SetDialogTitleCommand('#some-dialog', 'Example');
+    $expected = array(
+      'command' => 'setDialogOption',
+      'selector' => '#some-dialog',
+      'optionName' => 'title',
+      'optionValue' => 'Example',
+    );
 
+    $this->assertEqual($command->render(), $expected, 'SetDialogTitleCommand::render() with a selector returns a proper array.');
+  }
+
+  /**
+   * Tests that SetWindowLocationCommand objects can be constructed and rendered.
+   */
+  function testSetWindowLocationCommand() {
+    $command = new SetWindowLocationCommand('http://example.com');
+    $expected = array(
+      'command' => 'setWindowLocation',
+      'url' => 'http://example.com',
+    );
+
+    $this->assertEqual($command->render(), $expected, 'SetWindowLocationCommand::render() with the expected command array.');
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Ajax/DialogTest.php b/core/modules/system/lib/Drupal/system/Tests/Ajax/DialogTest.php
index d7d69da..5b801f8 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Ajax/DialogTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Ajax/DialogTest.php
@@ -13,20 +13,87 @@
 class DialogTest extends AjaxTestBase {
   public static function getInfo() {
     return array(
-      'name' => 'Dialog',
-      'description' => 'Performs tests on #ajax[\'dialog\'].',
+      'name' => 'AJAX dialogs commands',
+      'description' => 'Performs tests on opening and manipulate dialogs via AJAX commands.',
       'group' => 'AJAX',
     );
   }
 
   /**
-   * Ensure elements with #ajax['dialog'] render correctly.
+   * Test sending non-JS and AJAX requests to open and manipulate modals.
    */
   function testDialog() {
     // Ensure the elements render without notices or exceptions.
     $this->drupalGet('ajax-test/dialog');
 
-    // @todo What else should we assert?
+    // Set up variables for this test.
+    $dialog_renderable = ajax_test_dialog_contents();
+    $dialog_contents = drupal_render($dialog_renderable);
+    $modal_expected_response = array(
+      'command' => 'openDialog',
+      'selector' => '#drupal-modal',
+      'settings' => NULL,
+      'data' => $dialog_contents,
+      'dialogOptions' => array(
+        'modal' => true,
+        'title' => 'AJAX Dialog',
+      ),
+    );
+    $normal_expected_response = array(
+      'command' => 'openDialog',
+      'selector' => '#ajax-test-dialog-wrapper-1',
+      'settings' => NULL,
+      'data' => $dialog_contents,
+      'dialogOptions' => array(
+        'modal' => false,
+        'title' => 'AJAX Dialog',
+      ),
+    );
+    $close_expected_response = array(
+      'command' => 'closeDialog',
+      'selector' => '#ajax-test-dialog-wrapper-1',
+    );
+
+    // Check that requesting a modal dialog without JS goes to a page.
+    $this->drupalGet('ajax-test/dialog-contents/nojs/1');
+    $this->assertRaw($dialog_contents, 'Non-JS modal dialog page present.');
+
+    // Emulate going to the JS version of the page and check the JSON response.
+    $ajax_result = $this->drupalGetAJAX('ajax-test/dialog-contents/ajax/1');
+    $this->assertEqual($modal_expected_response, $ajax_result[1], 'Modal dialog JSON response matches.');
+
+    // Check that requesting a "normal" dialog without JS goes to a page.
+    $this->drupalGet('ajax-test/dialog-contents/nojs');
+    $this->assertRaw($dialog_contents, 'Non-JS normal dialog page present.');
+
+    // Emulate going to the JS version of the page and check the JSON response.
+    $ajax_result = $this->drupalGetAJAX('ajax-test/dialog-contents/ajax');
+    $this->assertEqual($normal_expected_response, $ajax_result[1], 'Normal dialog JSON response matches.');
+
+    // Emulate closing the dialog via an AJAX request. There is no non-JS
+    // version of this test.
+    $ajax_result = $this->drupalGetAJAX('ajax-test/dialog-close');
+    $this->assertEqual($close_expected_response, $ajax_result[0], 'Close dialog JSON response matches.');
+
+    // Test submitting via a POST request through the button for modals. This
+    // approach more accurately reflects the real responses by Drupal because
+    // all of the necessary page variables are emulated.
+    $ajax_result = $this->drupalPostAJAX('ajax-test/dialog', array(), 'button1');
+
+    // Check that CSS and JavaScript are "added" to the page dynamically.
+    $dialog_css_exists = strpos($ajax_result[1]['data'], 'jquery.ui.dialog.css') !== FALSE;
+    $this->assertTrue($dialog_css_exists, 'jQuery UI dialog CSS added to the page.');
+    $dialog_js_exists = strpos($ajax_result[2]['data'], 'jquery.ui.dialog.js') !== FALSE;
+    $this->assertTrue($dialog_css_exists, 'jQuery UI dialog JS added to the page.');
+    $dialog_js_exists = strpos($ajax_result[2]['data'], 'dialog.ajax.js') !== FALSE;
+    $this->assertTrue($dialog_css_exists, 'Drupal dialog JS added to the page.');
+
+    // Check that the response matches the expected value.
+    $this->assertEqual($modal_expected_response, $ajax_result[3], 'POST request modal dialog JSON response matches.');
+
+    // Abbreviated test for "normal" dialogs, testing only the difference.
+    $ajax_result = $this->drupalPostAJAX('ajax-test/dialog', array(), 'button2');
+    $this->assertEqual($normal_expected_response, $ajax_result[3], 'POST request normal dialog JSON response matches.');
   }
 
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Ajax/FrameworkTest.php b/core/modules/system/lib/Drupal/system/Tests/Ajax/FrameworkTest.php
index 8c408ed..219bb44 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Ajax/FrameworkTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Ajax/FrameworkTest.php
@@ -35,12 +35,6 @@ function testAJAXRender() {
     $commands = $this->drupalGetAJAX('ajax-test/render');
     $expected = new SettingsCommand(array('ajax' => 'test'), TRUE);
     $this->assertCommand($commands, $expected->render(), 'ajax_render() loads settings added with drupal_add_js().');
-
-    // Verify that JavaScript settings are loaded for #type 'link'.
-    $this->drupalGet('ajax-test/link');
-    $settings = $this->drupalGetSettings();
-    $this->assertEqual($settings['ajax']['ajax-link']['url'], url('filter/tips'));
-    $this->assertEqual($settings['ajax']['ajax-link']['wrapper'], 'block-system-main');
   }
 
   /**
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 2c51ae0..a9ee89a 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1305,6 +1305,19 @@ function system_library_info() {
     ),
   );
 
+  // Drupal's integration between AJAX and dialogs.
+  $libraries['drupal.dialog.ajax'] = array(
+    'title' => 'Drupal Dialog AJAX',
+    'version' => VERSION,
+    'js' => array(
+      'core/misc/dialog.ajax.js' => array('group' => JS_LIBRARY, 'weight' => 3),
+    ),
+    'dependencies' => array(
+      array('system', 'drupal.ajax'),
+      array('system', 'drupal.dialog'),
+    ),
+  );
+
   // Drupal's states library.
   $libraries['drupal.states'] = array(
     'title' => 'Drupal states',
diff --git a/core/modules/system/tests/modules/ajax_test/ajax_test.module b/core/modules/system/tests/modules/ajax_test/ajax_test.module
index c4b0503..1cb6bf2 100644
--- a/core/modules/system/tests/modules/ajax_test/ajax_test.module
+++ b/core/modules/system/tests/modules/ajax_test/ajax_test.module
@@ -6,6 +6,9 @@
  */
 
 use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\OpenDialogCommand;
+use Drupal\Core\Ajax\OpenModalDialogCommand;
+use Drupal\Core\Ajax\CloseDialogCommand;
 use Drupal\Core\Ajax\HtmlCommand;
 
 /**
@@ -31,11 +34,6 @@ function ajax_test_menu() {
     'access callback' => TRUE,
     'type' => MENU_CALLBACK,
   );
-  $items['ajax-test/link'] = array(
-    'title' => 'AJAX Link',
-    'page callback' => 'ajax_test_link',
-    'access callback' => TRUE,
-  );
   $items['ajax-test/dialog'] = array(
     'title' => 'AJAX Dialog',
     'page callback' => 'ajax_test_dialog',
@@ -46,6 +44,12 @@ function ajax_test_menu() {
     'page callback' => 'ajax_test_dialog_contents',
     'access callback' => TRUE,
   );
+  $items['ajax-test/dialog-close'] = array(
+    'title' => 'AJAX Dialog close',
+    'page callback' => 'ajax_test_dialog_close',
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+  );
   return $items;
 }
 
@@ -102,21 +106,6 @@ function ajax_test_error() {
 }
 
 /**
- * Menu callback: Renders a #type link with #ajax.
- */
-function ajax_test_link() {
-  $build['link'] = array(
-    '#type' => 'link',
-    '#title' => 'Show help',
-    '#href' => 'filter/tips',
-    '#ajax' => array(
-      'wrapper' => 'block-system-main',
-    ),
-  );
-  return $build;
-}
-
-/**
  * Menu callback: Renders a form elements and links with #ajax['dialog'].
  */
 function ajax_test_dialog() {
@@ -131,10 +120,8 @@ function ajax_test_dialog() {
   $build['link'] = array(
     '#type' => 'link',
     '#title' => 'Link 1 (modal)',
-    '#href' => 'ajax-test/dialog-contents',
-    '#ajax' => array(
-      'dialog' => array('modal' => TRUE),
-    ),
+    '#href' => 'ajax-test/dialog-contents/nojs/1',
+    '#attributes' => array('class' => array('use-ajax')),
   );
 
   // Dialog behavior applied to links rendered by theme_links().
@@ -143,18 +130,18 @@ function ajax_test_dialog() {
     '#links' => array(
       'link2' => array(
         'title' => 'Link 2 (modal)',
-        'href' => 'ajax-test/dialog-contents',
-        'ajax' => array(
-          'dialog' => array('modal' => TRUE),
-         ),
+        'href' => 'ajax-test/dialog-contents/nojs/1',
+        'attributes' => array('class' => array('use-ajax')),
       ),
       'link3' => array(
         'title' => 'Link 3 (non-modal)',
-        'href' => 'ajax-test/dialog-contents',
-        'ajax' => array(
-          'dialog' => array(),
-          'wrapper' => 'ajax-test-dialog-wrapper-2',
-        ),
+        'href' => 'ajax-test/dialog-contents/nojs',
+        'attributes' => array('class' => array('use-ajax')),
+      ),
+      'link4' => array(
+        'title' => 'Link 4 (close non-modal if open)',
+        'href' => 'ajax-test/dialog-close',
+        'attributes' => array('class' => array('use-ajax')),
       ),
     ),
   );
@@ -167,35 +154,50 @@ function ajax_test_dialog() {
 function ajax_test_dialog_form($form, &$form_state) {
   $form['button1'] = array(
     '#type' => 'submit',
+    '#name' => 'button1',
     '#value' => 'Button 1 (modal)',
     '#ajax' => array(
-      'dialog' => array('modal' => TRUE),
+      'callback' => 'ajax_test_dialog_form_callback_modal',
     ),
   );
   $form['button2'] = array(
     '#type' => 'submit',
+    '#name' => 'button2',
     '#value' => 'Button 2 (non-modal)',
     '#ajax' => array(
-      'dialog' => array(),
-      'wrapper' => 'ajax-test-dialog-wrapper-1',
+      'callback' => 'ajax_test_dialog_form_callback_nonmodal',
     ),
   );
   return $form;
 }
 
 /**
- * Form submit handler for ajax_test_dialog_form().
+ * Non-AJAX behavior of the dialog buttons.
  */
 function ajax_test_dialog_form_submit($form, &$form_state) {
   $form_state['redirect'] = 'ajax-test/dialog-contents';
 }
 
 /**
+ * AJAX callback handler for ajax_test_dialog_form().
+ */
+function ajax_test_dialog_form_callback_modal($form, &$form_state) {
+  return ajax_test_dialog_contents('ajax', TRUE);
+}
+
+/**
+ * AJAX callback handler for ajax_test_dialog_form().
+ */
+function ajax_test_dialog_form_callback_nonmodal($form, &$form_state) {
+  return ajax_test_dialog_contents('ajax', FALSE);
+}
+
+/**
  * Menu callback: Returns the contents for dialogs opened by ajax_test_dialog().
  */
-function ajax_test_dialog_contents() {
+function ajax_test_dialog_contents($page_mode = 'nojs', $is_modal = 0) {
   // This is a regular render array; the keys do not have special meaning.
-  return array(
+  $content = array(
     'content' => array(
       '#markup' => 'Example message',
     ),
@@ -210,5 +212,30 @@ function ajax_test_dialog_contents() {
       ),
     ),
   );
+
+  if ($page_mode === 'ajax') {
+    $response = new AjaxResponse();
+    $title = t('AJAX Dialog');
+    $html = drupal_render($content);
+    if ($is_modal) {
+      $response->addCommand(new OpenModalDialogCommand($title, $html));
+    }
+    else {
+      $selector = '#ajax-test-dialog-wrapper-1';
+      $response->addCommand(new OpenDialogCommand($selector, $title, $html));
+    }
+    return $response;
+  }
+  else {
+    return $content;
+  }
 }
 
+/**
+ * Menu callback: Close the ajax dialog.
+ */
+function ajax_test_dialog_close() {
+  $response = new AjaxResponse();
+  $response->addCommand(new CloseDialogCommand('#ajax-test-dialog-wrapper-1'));
+  return $response;
+}
