Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.1244
diff -u -p -r1.1244 common.inc
--- includes/common.inc	21 Oct 2010 19:31:39 -0000	1.1244
+++ includes/common.inc	24 Dec 2010 08:30:16 -0000
@@ -5616,8 +5616,9 @@ function drupal_render_cache_set(&$marku
   // be retrieved and used.
   $data['#markup'] = &$markup;
   // Persist attached data associated with this element.
-  if (isset($elements['#attached'])) {
-    $data['#attached'] = $elements['#attached'];
+  $attached = drupal_render_collect_attached($elements, TRUE);
+  if ($attached) {
+    $data['#attached'] = $attached;
   }
   $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'cache';
   $expire = isset($elements['#cache']['expire']) ? $elements['#cache']['expire'] : CACHE_PERMANENT;
@@ -5625,6 +5626,49 @@ function drupal_render_cache_set(&$marku
 }
 
 /**
+ * Collect #attached for an element and all child elements into a single array.
+ *
+ * When caching elements, it is necessary to collect all libraries, javascript
+ * and CSS into a single array, from both the element itself and all child
+ * elements. This allows drupal_render() to add these back to the page when the
+ * element is returned from cache.
+ *
+ * @param $elements
+ *   The element to collect #attached from.
+ * @param $return
+ *   Whether to return the attached elements and reset the internal static.
+ *
+ * @return
+ *   The #attached array for this element and its descendants.
+ */
+function drupal_render_collect_attached($elements, $return = FALSE) {
+  $attached = &drupal_static(__FUNCTION__, array());
+
+  // Collect all #attached for this element.
+  if (isset($elements['#attached'])) {
+    foreach ($elements['#attached'] as $key => $value) {
+      if (!isset($attached[$key])) {
+        $attached[$key] = array();
+      }
+    $attached[$key] = array_merge($attached[$key], $value);
+    }
+  }
+  if ($children = element_children($elements)) {
+    foreach ($children as $child) {
+      drupal_render_collect_attached($elements[$child]);
+    }
+  }
+
+  // If this was the first call to the function, return all attached elements
+  // and reset the static cache.
+  if ($return) {
+    $return = $attached;
+    $attached = array();
+    return $return;
+  }
+}
+
+/**
  * Prepare an element for caching based on a query. This smart caching strategy
  * saves Drupal from querying and rendering to HTML when the underlying query is
  * unchanged.
Index: modules/simpletest/tests/common.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/common.test,v
retrieving revision 1.130
diff -u -p -r1.130 common.test
--- modules/simpletest/tests/common.test	8 Oct 2010 15:36:12 -0000	1.130
+++ modules/simpletest/tests/common.test	24 Dec 2010 08:30:16 -0000
@@ -1476,6 +1476,64 @@ class DrupalRenderTestCase extends Drupa
   }
 
   /**
+   * Test #attached functionality in children elements.
+   */
+  function testDrupalRenderChildrenAttached() {
+    // The cache system is turned off for POST requests.
+    $request_method = $_SERVER['REQUEST_METHOD'];
+    $_SERVER['REQUEST_METHOD'] = 'GET';
+
+    // Create a cached element with a child and subchild.
+    $parent_js = drupal_get_path('module', 'user') . '/user.js';
+    $child_js = drupal_get_path('module', 'forum') . '/forum.js';
+    $subchild_js = drupal_get_path('module', 'book') . '/book.js';
+    $element = array(
+      '#type' => 'fieldset',
+      '#cache' => array(
+        'keys' => array('simpletest', 'drupal_render', 'children_attached'),
+        'bin' => 'cache',
+        'expire' => time() + 60,
+        'granularity' => DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE,
+      ),
+      '#attached' => array(
+        'js' => array($parent_js),
+      ),
+      '#title' => 'Parent',
+    );
+    $element['child'] = array(
+      '#type' => 'fieldset',
+      '#attached' => array(
+        'js' => array($child_js),
+      ),
+      '#title' => 'Child',
+    );
+    $element['child']['subchild'] = array(
+      '#attached' => array(
+        'js' => array($subchild_js),
+      ),
+      '#markup' => 'Subchild',
+    );
+
+    // Render the element and verify the presense of #attached Javascript.
+    drupal_render($element);
+    $scripts = drupal_get_js();
+    $this->assertTrue(strpos($scripts, $parent_js), t('The element #attached Javascript was included.'));
+    $this->assertTrue(strpos($scripts, $child_js), t('The child #attached Javascript was included.'));
+    $this->assertTrue(strpos($scripts, $subchild_js), t('The subchild #attached Javascript was included.'));
+
+    // Load the element from cache and verify the presense of #attached
+    // Javascript.
+    drupal_static_reset('drupal_add_js');
+    $this->assertTrue(drupal_render_cache_get($element), t('The element was retrieved from cache.'));
+    $scripts = drupal_get_js();
+    $this->assertTrue(strpos($scripts, $parent_js), t('The element #attached Javascript was included when loading from cache.'));
+    $this->assertTrue(strpos($scripts, $child_js), t('The child #attached Javascript was included when loading from cache.'));
+    $this->assertTrue(strpos($scripts, $subchild_js), t('The subchild #attached Javascript was included when loading from cache.'));
+
+    $_SERVER['REQUEST_METHOD'] = $request_method;
+  }
+
+  /**
    * Test passing arguments to the theme function.
    */
   function testDrupalRenderThemeArguments() {
