Index: includes/ajax.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/ajax.inc,v
retrieving revision 1.33
diff -u -p -r1.33 ajax.inc
--- includes/ajax.inc 27 Sep 2010 00:53:55 -0000  1.33
+++ includes/ajax.inc 30 Sep 2010 23:58:14 -0000
@@ -213,11 +213,66 @@
  *   functions.
  */
 function ajax_render($commands = array()) {
-  // Automatically extract any 'settings' added via drupal_add_js() and make
-  // them the first command.
-  $scripts = drupal_add_js(NULL, NULL);
+  // AJAX responses aren't rendered with html.tpl.php, so we have to call
+  // drupal_get_css() and drupal_get_js() here, in order to have new files added
+  // during this request to be loaded by the page. We only want to send back
+  // files that the page hasn't already loaded, so we implement simple diffing
+  // logic using array_diff_key().
+  foreach (array('css', 'js') as $type) {
+    // It is highly suspicious if $_POST['ajax_page_state'][$type] is empty,
+    // since the base page ought to have at least one JS file and one CSS file
+    // loaded. It probably indicates an error, and rather than making the page
+    // reload all of the files, instead we return no new files.
+    if (empty($_POST['ajax_page_state'][$type])) {
+      $items[$type] = array();
+    }
+    else {
+      $function = 'drupal_add_' . $type;
+      $items[$type] = $function();
+      drupal_alter($type, $items[$type]);
+      // @todo Inline CSS and JS items are indexed numerically. These can't be
+      //   reliably diffed with array_diff_key(), since the number can change
+      //   due to factors unrelated to the inline content, so for now, we strip
+      //   the inline items from AJAX responses, and can add support for them
+      //   when drupal_add_css() and drupal_add_js() are changed to using md5()
+      //   or some other hash of the inline content.
+      foreach ($items[$type] as $key => $item) {
+        if (is_numeric($key)) {
+          unset($items[$type][$key]);
+        }
+      }
+      // Ensure that the page doesn't reload what it already has.
+      $items[$type] = array_diff_key($items[$type], $_POST['ajax_page_state'][$type]);
+    }
+  }
+
+  // Render the HTML to load these files, and add AJAX commands to insert this
+  // HTML in the page. We pass TRUE as second argument to prevent the data from
+  // being altered again, as we already altered it above.
+  $styles = drupal_get_css($items['css'], TRUE);
+  $scripts_footer = drupal_get_js('footer', $items['js'], TRUE);
+  $scripts_header = drupal_get_js('header', $items['js'], TRUE);
+
+  $extra_commands = array();
+  if (!empty($styles)) {
+    $extra_commands[] = ajax_command_prepend('head', $styles);
+  }
+  if (!empty($scripts_header)) {
+    $extra_commands[] = ajax_command_prepend('head', $scripts_header);
+  }
+  if (!empty($scripts_footer)) {
+    $extra_commands[] = ajax_command_append('body', $scripts_footer);
+  }
+  if (!empty($extra_commands)) {
+    $commands = array_merge($extra_commands, $commands);
+  }
+
+  $scripts = drupal_add_js();
   if (!empty($scripts['settings'])) {
-    array_unshift($commands, ajax_command_settings(call_user_func_array('array_merge_recursive', $scripts['settings']['data'])));
+    $settings = $scripts['settings'];
+    // Automatically extract any settings added via drupal_add_js() and make
+    // them the first command.
+    array_unshift($commands, ajax_command_settings(call_user_func_array('array_merge_recursive', $settings['data']), TRUE));
   }

   // Allow modules to alter any AJAX response.
@@ -307,6 +362,31 @@ function ajax_form_callback() {
 }

 /**
+ * Theme callback for AJAX requests.
+ *
+ * Many different pages can invoke an AJAX request to system/ajax or another
+ * generic AJAX path. It is almost always desired for an AJAX response to be
+ * rendered using the same theme as the base page, and it is therefore
+ * recommended for all AJAX paths to list this function for its 'theme
+ * callback', as is done in system_menu() for the 'system/ajax' path.
+ */
+function ajax_base_page_theme() {
+  if (!empty($_POST['ajax_page_state']['theme']) && !empty($_POST['ajax_page_state']['theme_token'])) {
+    $theme = $_POST['ajax_page_state']['theme'];
+    $token = $_POST['ajax_page_state']['theme_token'];
+
+    // Prevent a request forgery from giving a person access to a theme they
+    // shouldn't be otherwise allowed to see. However, since everyone is allowed
+    // to see the default theme, token validation isn't required for that, and
+    // bypassing it allows most use-cases to work even when accessed from the
+    // page cache.
+    if ($theme === variable_get('theme_default', 'bartik') || drupal_valid_token($token, $theme)) {
+      return $theme;
+    }
+  }
+}
+
+/**
  * Package and send the result of a page callback to the browser as an AJAX response.
  *
  * @param $page_callback_result
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.1228
diff -u -p -r1.1228 common.inc
--- includes/common.inc 26 Sep 2010 23:31:35 -0000  1.1228
+++ includes/common.inc 30 Sep 2010 23:58:19 -0000
@@ -2822,16 +2822,22 @@ function drupal_add_css($data = NULL, $o
  * @param $css
  *   (optional) An array of CSS files. If no array is provided, the default
  *   stylesheets array is used instead.
+ * @param $skip_alter
+ *   (optional) If set to TRUE, this function skips calling drupal_alter() on
+ *   $css, useful when the calling function passes a $css array that has already
+ *   been altered.
  * @return
  *   A string of XHTML CSS tags.
  */
-function drupal_get_css($css = NULL) {
+function drupal_get_css($css = NULL, $skip_alter = FALSE) {
   if (!isset($css)) {
     $css = drupal_add_css();
   }

   // Allow modules and themes to alter the CSS items.
-  drupal_alter('css', $css);
+  if (!$skip_alter) {
+    drupal_alter('css', $css);
+  }

   // Sort CSS items according to their weights.
   uasort($css, 'drupal_sort_weight');
@@ -2855,6 +2861,12 @@ function drupal_get_css($css = NULL) {
     '#type' => 'styles',
     '#items' => $css,
   );
+
+  // Provide the page with information about the individual CSS files used,
+  // information not otherwise available when CSS aggregation is enabled.
+  $setting['ajaxPageState']['css'] = array_fill_keys(array_keys($css), 1);
+  $styles['#attached']['js'][] = array('type' => 'setting', 'data' => $setting);
+
   return drupal_render($styles);
 }

@@ -3791,13 +3803,17 @@ function drupal_js_defaults($data = NULL
  * @param $javascript
  *   (optional) An array with all JavaScript code. Defaults to the default
  *   JavaScript array for the given scope.
+ * @param $skip_alter
+ *   (optional) If set to TRUE, this function skips calling drupal_alter() on
+ *   $javascript, useful when the calling function passes a $javascript array
+ *   that has already been altered.
  * @return
  *   All JavaScript code segments and includes for the scope as HTML tags.
  * @see drupal_add_js()
  * @see locale_js_alter()
  * @see drupal_js_defaults()
  */
-function drupal_get_js($scope = 'header', $javascript = NULL) {
+function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALSE) {
   if (!isset($javascript)) {
     $javascript = drupal_add_js();
   }
@@ -3806,13 +3822,15 @@ function drupal_get_js($scope = 'header'
   }

   // Allow modules to alter the JavaScript.
-  drupal_alter('js', $javascript);
+  if (!$skip_alter) {
+    drupal_alter('js', $javascript);
+  }

   // Filter out elements of the given scope.
   $items = array();
-  foreach ($javascript as $item) {
+  foreach ($javascript as $key => $item) {
     if ($item['scope'] == $scope) {
-      $items[] = $item;
+      $items[$key] = $item;
     }
   }

@@ -3845,6 +3863,22 @@ function drupal_get_js($scope = 'header'
   // Sort the JavaScript by weight so that it appears in the correct order.
   uasort($items, 'drupal_sort_weight');

+  // Provide the page with information about the individual JavaScript files
+  // used, information not otherwise available when aggregation is enabled.
+  $setting['ajaxPageState']['js'] = array_fill_keys(array_keys($items), 1);
+  unset($setting['ajaxPageState']['js']['settings']);
+  drupal_add_js($setting, 'setting');
+
+  // If we're outputting the header scope, then this might be the final time
+  // that drupal_get_js() is running, so add the setting to this output as well
+  // as to the drupal_add_js() cache. If $items['settings'] doesn't exist, it's
+  // because drupal_get_js() was intentionally passed a $javascript argument
+  // stripped off settings, potentially in order to override how settings get
+  // output, so in this case, do not add the setting to this output.
+  if ($scope == 'header' && isset($items['settings'])) {
+    $items['settings']['data'][] = $setting;
+  }
+
   // Loop through the JavaScript to construct the rendered output.
   $element = array(
     '#tag' => 'script',
Index: includes/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.615
diff -u -p -r1.615 theme.inc
--- includes/theme.inc  26 Sep 2010 23:31:35 -0000  1.615
+++ includes/theme.inc  30 Sep 2010 23:58:21 -0000
@@ -102,6 +102,15 @@ function drupal_theme_initialize() {

   // Themes can have alter functions, so reset the drupal_alter() cache.
   drupal_static_reset('drupal_alter');
+
+  // Provide the page with information about the theme that's used, so that a
+  // later AJAX request can be rendered using the same theme.
+  // @see ajax_base_page_theme()
+  $setting['ajaxPageState'] = array(
+    'theme' => $theme_key,
+    'themeToken' => drupal_get_token($theme_key),
+  );
+  drupal_add_js($setting, 'setting');
 }

 /**
Index: misc/ajax.js
===================================================================
RCS file: /cvs/drupal/drupal/misc/ajax.js,v
retrieving revision 1.19
diff -u -p -r1.19 ajax.js
--- misc/ajax.js  22 Sep 2010 21:01:39 -0000  1.19
+++ misc/ajax.js  30 Sep 2010 23:58:21 -0000
@@ -231,6 +231,17 @@ Drupal.ajax.prototype.beforeSubmit = fun
     form_values.push({ name: 'ajax_html_ids[]', value: this.id });
   });

+  // Allow Drupal to return new JavaScript and CSS files to load without
+  // returning the ones already loaded.
+  form_values.push({ name: 'ajax_page_state[theme]', value: Drupal.settings.ajaxPageState.theme });
+  form_values.push({ name: 'ajax_page_state[theme_token]', value: Drupal.settings.ajaxPageState.themeToken });
+  for (var key in Drupal.settings.ajaxPageState.css) {
+    form_values.push({ name: 'ajax_page_state[css][' + key + ']', value: 1 });
+  }
+  for (var key in Drupal.settings.ajaxPageState.js) {
+    form_values.push({ name: 'ajax_page_state[js][' + key + ']', value: 1 });
+  }
+
   // Insert progressbar or throbber.
   if (this.progress.type == 'bar') {
     var progressBar = new Drupal.progressBar('ajax-progress-' + this.element.id, eval(this.progress.update_callback), this.progress.method, eval(this.progress.error_callback));
Index: modules/file/file.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/file/file.module,v
retrieving revision 1.40
diff -u -p -r1.40 file.module
--- modules/file/file.module  13 Sep 2010 01:09:25 -0000  1.40
+++ modules/file/file.module  30 Sep 2010 23:58:22 -0000
@@ -41,12 +41,14 @@ function file_menu() {
     'page callback' => 'file_ajax_upload',
     'delivery callback' => 'ajax_deliver',
     'access arguments' => array('access content'),
+    'theme callback' => 'ajax_base_page_theme',
     'type' => MENU_CALLBACK,
   );
   $items['file/progress'] = array(
     'page callback' => 'file_ajax_progress',
     'delivery callback' => 'ajax_deliver',
     'access arguments' => array('access content'),
+    'theme callback' => 'ajax_base_page_theme',
     'type' => MENU_CALLBACK,
   );

Index: modules/simpletest/drupal_web_test_case.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v
retrieving revision 1.238
diff -u -p -r1.238 drupal_web_test_case.php
--- modules/simpletest/drupal_web_test_case.php 28 Sep 2010 03:30:37 -0000  1.238
+++ modules/simpletest/drupal_web_test_case.php 30 Sep 2010 23:58:25 -0000
@@ -2816,7 +2816,7 @@ class DrupalWebTestCase extends DrupalTe
    */
   protected function assertFieldByXPath($xpath, $value, $message = '', $group = 'Other') {
     $fields = $this->xpath($xpath);
-
+    debug($fields);
     // If value specified then check array for match.
     $found = TRUE;
     if ($value) {
Index: modules/simpletest/tests/ajax.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/ajax.test,v
retrieving revision 1.18
diff -u -p -r1.18 ajax.test
--- modules/simpletest/tests/ajax.test  28 Sep 2010 02:30:32 -0000  1.18
+++ modules/simpletest/tests/ajax.test  30 Sep 2010 23:58:25 -0000
@@ -2,27 +2,52 @@
 // $Id: ajax.test,v 1.18 2010/09/28 02:30:32 dries Exp $

 class AJAXTestCase extends DrupalWebTestCase {
+  protected $profile = 'standard';
+
   function setUp() {
     $modules = func_get_args();
     if (isset($modules[0]) && is_array($modules[0])) {
       $modules = $modules[0];
     }
-    parent::setUp(array_unique(array_merge(array('ajax_test', 'ajax_forms_test'), $modules)));
+    parent::setUp(array_merge(array('ajax_test', 'ajax_forms_test'), $modules));
   }

   /**
-   * Returns the passed-in commands array without the initial settings command.
+   * Search through a settings return; find/return a setting.
    *
-   * Depending on factors that may be irrelevant to a particular test,
-   * ajax_render() may prepend a settings command. This function allows the test
-   * to only have to concern itself with the commands that were passed to
-   * ajax_render().
+   * @param $commands
+   *   The AJAX commands returned by the server.
+   * @param $conditions
+   *   Array of info we're looking to find in the return.
+   * @param $message
+   *   An assertion message.
    */
-  protected function discardSettings($commands) {
-    if ($commands[0]['command'] == 'settings') {
-      array_shift($commands);
+  protected function assertCommand($json, $conditions, $message) {
+    //it could be that we really want to test on the first array of the ajax
+    //handling so we should not remove the settings array
+    if ($json[0]['command'] == 'settings' && (count($json) > 1)) {
+      array_shift($json);
+    }
+    // @todo Counting success is bad. We have unique expectations.
+    // counting success should be outside of the foreach to not
+    // overwrite possible successes
+    $count_success = 0;
+    foreach ($json as $item) {
+      // Can't anticipate ajaxPageState.
+      if (isset($item['settings']['ajaxPageState'])) {
+        unset($item['settings']['ajaxPageState']);
+      }
+
+      // @todo Compare $item to $conditions (rename to $command) to make any sense of this.
+      foreach ($conditions as $key => $value) {
+        // @todo This should be a type-agnostic comparison. But let's remove
+        //   this entire inner foreach instead.
+        if (isset($item[$key]) && $item[$key] == $value) {
+          $count_success++;
+        }
+      }
     }
-    return $commands;
+    $this->assertEqual(count($conditions), $count_success, $message);
   }
 }

@@ -42,27 +67,46 @@ class AJAXFrameworkTestCase extends AJAX
    * Test proper passing of JavaScript settings via ajax_render().
    */
   function testAJAXRender() {
-    $result = $this->drupalGetAJAX('ajax-test/render');
-    // Verify that JavaScript settings are contained (always first).
-    $this->assertIdentical($result[0]['command'], 'settings', t('drupal_add_js() settings are contained first.'));
-    // Verify that basePath is contained in JavaScript settings.
-    $this->assertEqual($result[0]['settings']['basePath'], base_path(), t('Base path is contained in JavaScript settings.'));
+    $commands = $this->drupalGetAJAX('ajax-test/render');
+    // Verify that there is a command to load settings added with
+    // drupal_add_js().
+
+    $result = $this->assertCommand($commands, array(
+      'command' => 'settings',
+    ), t('ajax_render() loads settings added with drupal_add_js().'));
+
+    $result = $this->assertCommand($commands, array(
+      'settings' => array('basePath' => base_path(), 'ajax' => 'test'),
+    ), t('The %setting setting is included.', array('%setting' => 'basePath')));
+
+    // Verify that our file is included in the list.
+    $file = file_create_url('misc/drupal.js');
+    foreach ($commands as $command) {
+      if ($command['command'] == 'scripts') {
+        $this->assertEqual($command['files'][$file], $file, t('The %file file is included.', array('%file' => 'misc/drupal.js')));
+      }
+    }
   }

   /**
    * Test behavior of ajax_render_error().
    */
   function testAJAXRenderError() {
-    $result = $this->discardSettings($this->drupalGetAJAX('ajax-test/render-error'));
+    $commands = $this->drupalGetAJAX('ajax-test/render-error');
     // Verify default error message.
-    $this->assertEqual($result[0]['command'], 'alert', t('ajax_render_error() invokes alert command.'));
-    $this->assertEqual($result[0]['text'], t('An error occurred while handling the request: The server received invalid input.'), t('Default error message is output.'));
+    $result = $this->assertCommand($commands, array(
+      'command' => 'alert',
+      'text' => t('An error occurred while handling the request: The server received invalid input.'),
+    ), t('ajax_render_error() invokes alert command.'));
+
     // Verify custom error message.
     $edit = array(
       'message' => 'Custom error message.',
     );
-    $result = $this->discardSettings($this->drupalGetAJAX('ajax-test/render-error', array('query' => $edit)));
-    $this->assertEqual($result[0]['text'], $edit['message'], t('Custom error message is output.'));
+    $commands = $this->drupalGetAJAX('ajax-test/render-error', array('query' => $edit));
+    $result = $this->assertCommand($commands, array(
+      'text' => $edit['message'],
+    ), t('Custom error message is output.'));
   }
 }

@@ -84,11 +128,16 @@ class AJAXCommandsTestCase extends AJAXT
   function testAJAXRender() {
     $commands = array();
     $commands[] = ajax_command_settings(array('foo' => 42));
-    $result = $this->drupalGetAJAX('ajax-test/render', array('query' => array('commands' => $commands)));
-    // Verify that JavaScript settings are contained (always first).
-    $this->assertIdentical($result[0]['command'], 'settings', t('drupal_add_js() settings are contained first.'));
+
+    $commands = $this->drupalGetAJAX('ajax-test/render', array(
+      'query' => array('commands' => $commands),
+    ));
+
     // Verify that the custom setting is contained.
-    $this->assertEqual($result[1]['settings']['foo'], 42, t('Custom setting is output.'));
+    $result = $this->assertCommand($commands, array(
+      'command' => 'settings',
+      'settings' => array('foo' => 42),
+    ), t('Custom setting is output.'));
   }

   /**
@@ -102,69 +151,104 @@ class AJAXCommandsTestCase extends AJAXT
     $edit = array();

     // Tests the 'after' command.
-    $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'After': Click to put something after the div"))));
-    $command = $commands[0];
-    $this->assertTrue($command['command'] == 'insert' && $command['method'] == 'after' && $command['data'] == 'This will be placed after', "'after' AJAX command issued with correct data");
+    $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'After': Click to put something after the div")));
+    $result = $this->assertCommand($commands, array(
+      'command' => 'insert',
+      'method' => 'after',
+      'data' => 'This will be placed after',
+    ), "'after' AJAX command issued with correct data");

     // Tests the 'alert' command.
-    $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'Alert': Click to alert"))));
-    $command = $commands[0];
-    $this->assertTrue($command['command'] == 'alert' && $command['text'] == 'Alert', "'alert' AJAX Command issued with correct text");
+    $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'Alert': Click to alert")));
+    $result = $this->assertCommand($commands, array(
+      'command' => 'alert',
+      'text' => 'Alert',
+    ), "'alert' AJAX Command issued with correct text");

     // Tests the 'append' command.
-    $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'Append': Click to append something"))));
-    $command = $commands[0];
-    $this->assertTrue($command['command'] == 'insert' && $command['method'] == 'append' && $command['data'] == 'Appended text', "'append' AJAX command issued with correct data");
+    $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'Append': Click to append something")));
+    $result = $this->assertCommand($commands, array(
+      'command' => 'insert',
+      'method' => 'append',
+      'data' => 'Appended text',
+    ), "'append' AJAX command issued with correct data");

     // Tests the 'before' command.
-    $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'before': Click to put something before the div"))));
-    $command = $commands[0];
-    $this->assertTrue($command['command'] == 'insert' && $command['method'] == 'before' && $command['data'] == 'Before text', "'before' AJAX command issued with correct data");
+    $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'before': Click to put something before the div")));
+    $result = $this->assertCommand($commands, array(
+      'command' => 'insert',
+      'method' => 'before',
+      'data' => 'Before text',
+    ), "'before' AJAX command issued with correct data");

     // Tests the 'changed' command.
-    $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX changed: Click to mark div changed."))));
-    $command = $commands[0];
-    $this->assertTrue($command['command'] == 'changed' && $command['selector'] == '#changed_div', "'changed' AJAX command issued with correct selector");
+    $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX changed: Click to mark div changed.")));
+    $result = $this->assertCommand($commands, array(
+      'command' => 'changed',
+      'selector' => '#changed_div',
+    ), "'changed' AJAX command issued with correct selector");

     // Tests the 'changed' command using the second argument.
-    $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX changed: Click to mark div changed with asterisk."))));
-    $command = $commands[0];
-    $this->assertTrue($command['command'] == 'changed' && $command['selector'] == '#changed_div' && $command['asterisk'] == '#changed_div_mark_this', "'changed' AJAX command (with asterisk) issued with correct selector");
+    $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX changed: Click to mark div changed with asterisk.")));
+    $result = $this->assertCommand($commands, array(
+      'command' => 'changed',
+      'selector' => '#changed_div',
+      'asterisk' => '#changed_div_mark_this',
+    ), "'changed' AJAX command (with asterisk) issued with correct selector");

     // Tests the 'css' command.
-    $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("Set the the '#box' div to be blue."))));
-    $command = $commands[0];
-    $this->assertTrue($command['command'] == 'css' && $command['selector'] == '#css_div' && $command['argument']['background-color'] == 'blue', "'css' AJAX command issued with correct selector");
+    $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("Set the the '#box' div to be blue.")));
+
+    $result = $this->assertCommand($commands, array(
+      'command' => 'css',
+      'selector' => '#css_div',
+      'argument' => array('background-color' => 'blue'),
+    ), "'css' AJAX command issued with correct selector");

     // Tests the 'data' command.
-    $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX data command: Issue command."))));
-    $command = $commands[0];
-    $this->assertTrue($command['command'] == 'data' && $command['name'] == 'testkey' && $command['value'] == 'testvalue', "'data' AJAX command issued with correct key and value");
+    $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX data command: Issue command.")));
+    $result = $this->assertCommand($commands, array(
+      'command' => 'data',
+      'name' => 'testkey',
+      'value' => 'testvalue',
+    ), "'data' AJAX command issued with correct key and value");

     // Tests the 'html' command.
-    $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX html: Replace the HTML in a selector."))));
-    $command = $commands[0];
-    $this->assertTrue($command['command'] == 'insert' && $command['method'] == 'html' && $command['data'] == 'replacement text', "'html' AJAX command issued with correct data");
+    $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX html: Replace the HTML in a selector.")));
+    $result = $this->assertCommand($commands, array(
+      'command' => 'insert',
+      'method' => 'html',
+      'data' => 'replacement text',
+    ), "'html' AJAX command issued with correct data");

     // Tests the 'insert' command.
-    $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX insert: Let client insert based on #ajax['method']."))));
-    $command = $commands[0];
-    $this->assertTrue($command['command'] == 'insert' && $command['method'] == NULL && $command['data'] == 'insert replacement text', "'insert' AJAX command issued with correct data");
+    $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX insert: Let client insert based on #ajax['method'].")));
+    $result = $this->assertCommand($commands, array(
+      'command' => 'insert',
+      'data' => 'insert replacement text',
+    ), "'insert' AJAX command issued with correct data");

     // Tests the 'prepend' command.
-    $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'prepend': Click to prepend something"))));
-    $command = $commands[0];
-    $this->assertTrue($command['command'] == 'insert' && $command['method'] == 'prepend' && $command['data'] == 'prepended text', "'prepend' AJAX command issued with correct data");
+    $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'prepend': Click to prepend something")));
+    $result = $this->assertCommand($commands, array(
+      'command' => 'insert',
+      'method' => 'prepend',
+      'data' => 'prepended text',
+    ), "'prepend' AJAX command issued with correct data");

     // Tests the 'remove' command.
-    $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'remove': Click to remove text"))));
-    $command = $commands[0];
-    $this->assertTrue($command['command'] == 'remove' && $command['selector'] == '#remove_text', "'remove' AJAX command issued with correct command and selector");
+    $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'remove': Click to remove text")));
+    $result = $this->assertCommand($commands, array(
+      'command' => 'remove',
+      'selector' => '#remove_text',
+    ), "'remove' AJAX command issued with correct command and selector");

     // Tests the 'restripe' command.
-    $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'restripe' command"))));
-    $command = $commands[0];
-    $this->assertTrue($command['command'] == 'restripe' && $command['selector'] == '#restripe_table', "'restripe' AJAX command issued with correct selector");
+    $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'restripe' command")));
+    $result = $this->assertCommand($commands, array(
+      'command' => 'restripe',
+      'selector' => '#restripe_table',
+    ), "'restripe' AJAX command issued with correct selector");
   }
 }

@@ -196,19 +280,21 @@ class AJAXFormValuesTestCase extends AJA
       $edit = array(
         'select' => $item,
       );
-      $commands = $this->discardSettings($this->drupalPostAJAX('ajax_forms_test_get_form', $edit, 'select'));
-      $data_command = $commands[1];
-      $this->assertEqual($data_command['value'], $item);
+      $commands = $this->drupalPostAJAX('ajax_forms_test_get_form', $edit, 'select');
+      $result = $this->assertCommand($commands, array(
+        'value' => $item,
+      ), "verification of AJAX form values from a selectbox issued with a correct value");
     }
-
     // Verify form values of a checkbox element.
     foreach (array(FALSE, TRUE) as $item) {
       $edit = array(
         'checkbox' => $item,
       );
-      $commands = $this->discardSettings($this->drupalPostAJAX('ajax_forms_test_get_form', $edit, 'checkbox'));
-      $data_command = $commands[1];
-      $this->assertEqual((int) $data_command['value'], (int) $item);
+      $commands = $this->drupalPostAJAX('ajax_forms_test_get_form', $edit, 'checkbox');
+
+      $result = $this->assertCommand($commands, array(
+        'value' => $item,
+      ), "verification of AJAX form values from a checkbox issued with a correct value");
     }
   }
 }
@@ -226,8 +312,18 @@ class AJAXMultiFormTestCase extends AJAX
   }

   function setUp() {
-    parent::setUp(array('form_test'));
+    parent::setUp('form_test');
+
+
+    $this->web_user = $this->drupalCreateUser(array('access content'));
+    $this->drupalLogin($this->web_user);
+
+  }

+  /**
+   * Test that a page with the 'page_node_form' included twice works correctly.
+   */
+  function testMultiForm() {
     // Create a multi-valued field for 'page' nodes to use for AJAX testing.
     $field_name = 'field_ajax_test';
     $field = array(
@@ -243,15 +339,10 @@ class AJAXMultiFormTestCase extends AJAX
     );
     field_create_instance($instance);

-    // Login a user who can create 'page' nodes.
+     // Log in a user who can create 'page' nodes.
     $this->web_user = $this->drupalCreateUser(array('create page content'));
     $this->drupalLogin($this->web_user);
-  }

-  /**
-   * Test that a page with the 'page_node_form' included twice works correctly.
-   */
-  function testMultiForm() {
     // HTML IDs for elements within the field are potentially modified with
     // each AJAX submission, but these variables are stable and help target the
     // desired elements.
@@ -268,9 +359,12 @@ class AJAXMultiFormTestCase extends AJAX
     // Ensure the initial page contains both node forms and the correct number
     // of field items and "add more" button for the multi-valued field within
     // each form.
+
     $this->drupalGet('form-test/two-instances-of-same-form');
+
     foreach ($field_xpaths as $form_html_id => $field_xpath) {
       $this->assert(count($this->xpath($field_xpath . $field_items_xpath_suffix)) == 1, t('Found the correct number of field items on the initial page.'));
+      //@todo When NULL is given as a value the assertFieldByXPath doesn't work
       $this->assertFieldByXPath($field_xpath . $button_xpath_suffix, NULL, t('Found the "add more" button on the initial page.'));
     }
     $this->assertNoDuplicateIds(t('Initial page contains unique IDs'), 'Other');
@@ -278,7 +372,7 @@ class AJAXMultiFormTestCase extends AJAX
     // Submit the "add more" button of each form twice. After each corresponding
     // page update, ensure the same as above.
     foreach ($field_xpaths as $form_html_id => $field_xpath) {
-      for ($i=0; $i<2; $i++) {
+      for ($i = 0; $i < 2; $i++) {
         $this->drupalPostAJAX(NULL, array(), array($button_name => $button_value), 'system/ajax', array(), array(), $form_html_id);
         $this->assert(count($this->xpath($field_xpath . $field_items_xpath_suffix)) == $i+2, t('Found the correct number of field items after an AJAX submission.'));
         $this->assertFieldByXPath($field_xpath . $button_xpath_suffix, NULL, t('Found the "add more" button after an AJAX submission.'));
@@ -309,9 +403,7 @@ class AJAXElementValidation extends AJAX
    * AJAX-enabled field fails due to the required field being empty.
    */
   function testAJAXElementValidation() {
-    $web_user = $this->drupalCreateUser();
     $edit = array('drivertext' => t('some dumb text'));
-
     // Post with 'drivertext' as the triggering element.
     $post_result = $this->drupalPostAJAX('ajax_validation_test', $edit, 'drivertext');
     // Look for a validation failure in the resultant JSON.
@@ -319,4 +411,3 @@ class AJAXElementValidation extends AJAX
     $this->assertText('ajax_forms_test_validation_form_callback invoked', t('The correct callback was invoked'));
   }
 }
-
Index: modules/simpletest/tests/ajax_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/ajax_test.module,v
retrieving revision 1.3
diff -u -p -r1.3 ajax_test.module
--- modules/simpletest/tests/ajax_test.module 13 Mar 2010 06:55:50 -0000  1.3
+++ modules/simpletest/tests/ajax_test.module 30 Sep 2010 23:58:25 -0000
@@ -31,7 +31,8 @@ function ajax_test_menu() {
  * Menu callback; Returns $_GET['commands'] suitable for use by ajax_deliver().
  *
  * Additionally ensures that ajax_render() incorporates JavaScript settings
- * by invoking drupal_add_js() with a dummy setting.
+ * and files by invoking drupal_add_js() with a dummy setting, which causes
+ * drupal_add_js() to also automatically add "misc/drupal.js".
  */
 function ajax_test_render() {
   // Prepare AJAX commands.
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.967
diff -u -p -r1.967 system.module
--- modules/system/system.module  29 Sep 2010 16:06:11 -0000  1.967
+++ modules/system/system.module  30 Sep 2010 23:58:29 -0000
@@ -514,6 +514,7 @@ function system_menu() {
     'page callback' => 'ajax_form_callback',
     'delivery callback' => 'ajax_deliver',
     'access callback' => TRUE,
+    'theme callback' => 'ajax_base_page_theme',
     'type' => MENU_CALLBACK,
     'file path' => 'includes',
     'file' => 'form.inc',
