Index: includes/ajax.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/ajax.inc,v
retrieving revision 1.31
diff -u -p -r1.31 ajax.inc
--- includes/ajax.inc	30 Apr 2010 08:07:54 -0000	1.31
+++ includes/ajax.inc	2 Sep 2010 00:39:09 -0000
@@ -193,11 +193,65 @@
  *   functions.
  */
 function ajax_render($commands = array()) {
+  // 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]);
+    }
+  }
+  // Settings are handled separately (see below).
+  unset($items['js']['settings']);
+
+  // Render the HTML to load these files, and add AJAX commands to insert this
+  // HTML in the page.
+  $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);
+  }
+
   // Automatically extract any 'settings' added via drupal_add_js() and make
   // them the first command.
-  $scripts = drupal_add_js(NULL, NULL);
+  $scripts = drupal_add_js();
   if (!empty($scripts['settings'])) {
-    array_unshift($commands, ajax_command_settings(call_user_func_array('array_merge_recursive', $scripts['settings']['data'])));
+    array_unshift($commands, ajax_command_settings(call_user_func_array('array_merge_recursive', $scripts['settings']['data']), TRUE));
   }
 
   // Allow modules to alter any AJAX response.
@@ -284,6 +338,30 @@ 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'])) {
+    list($theme, $token) = array($_POST['ajax_page_state']['theme'], $_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.1211
diff -u -p -r1.1211 common.inc
--- includes/common.inc	27 Aug 2010 11:54:32 -0000	1.1211
+++ includes/common.inc	2 Sep 2010 00:39:10 -0000
@@ -2811,16 +2811,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');
@@ -2844,6 +2850,10 @@ 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), TRUE);
+  $styles['#attached']['js'][] = array('type' => 'setting', 'data' => $setting);
   return drupal_render($styles);
 }
 
@@ -3780,13 +3790,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();
   }
@@ -3795,13 +3809,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;
     }
   }
 
@@ -3834,6 +3850,20 @@ 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 JS aggregation is enabled.
+  $setting['ajaxPageState']['js'] = array_fill_keys(array_keys($items), TRUE);
+  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 of 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.606
diff -u -p -r1.606 theme.inc
--- includes/theme.inc	22 Aug 2010 12:46:21 -0000	1.606
+++ includes/theme.inc	2 Sep 2010 00:39:11 -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.18
diff -u -p -r1.18 ajax.js
--- misc/ajax.js	25 Jun 2010 20:34:07 -0000	1.18
+++ misc/ajax.js	2 Sep 2010 00:39:12 -0000
@@ -212,6 +212,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.37
diff -u -p -r1.37 file.module
--- modules/file/file.module	27 Aug 2010 11:54:32 -0000	1.37
+++ modules/file/file.module	2 Sep 2010 00:39:15 -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/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.957
diff -u -p -r1.957 system.module
--- modules/system/system.module	22 Aug 2010 13:52:58 -0000	1.957
+++ modules/system/system.module	2 Sep 2010 00:39:19 -0000
@@ -509,6 +509,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',
