diff --git a/core/includes/common.inc b/core/includes/common.inc
index 9601c76..fe03068 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -5485,27 +5485,6 @@ function drupal_pre_render_dropbutton($element) {
 }
 
 /**
- * Pre-render callback: Appends contents in #markup to #children.
- *
- * This needs to be a #pre_render callback, because eventually assigned
- * #theme_wrappers will expect the element's rendered content in #children.
- * Note that if also a #theme is defined for the element, then the result of
- * the theme callback will override #children.
- *
- * @param $elements
- *   A structured array using the #markup key.
- *
- * @return
- *   The passed-in elements, but #markup appended to #children.
- *
- * @see drupal_render()
- */
-function drupal_pre_render_markup($elements) {
-  $elements['#children'] = $elements['#markup'];
-  return $elements;
-}
-
-/**
  * Renders the page, including all theming.
  *
  * @param $page
@@ -5687,11 +5666,20 @@ function drupal_render(&$elements) {
   // If #theme was not set and the element has children, render them now.
   // This is the same process as drupal_render_children() but is inlined
   // for speed.
-  if ($elements['#children'] == '') {
+  if ($elements['#children'] === '') {
     foreach ($children as $key) {
       $elements['#children'] .= drupal_render($elements[$key]);
     }
   }
+  // If #theme was not set, but the element has raw #markup, prepend the content
+  // in #markup to #children. #children may contain the rendered content
+  // supplied by #theme, or the rendered child elements, as processed above. If
+  // both #theme and #markup are set, then #theme is responsible for rendering
+  // the element. Eventually assigned #theme_wrappers will expect both the
+  // element's #markup and the rendered content of child elements in #children.
+  if (!isset($elements['#theme']) && isset($elements['#markup'])) {
+    $elements['#children'] = $elements['#markup'] . $elements['#children'];
+  }
 
   // Let the theme functions in #theme_wrappers add markup around the rendered
   // children.
diff --git a/core/includes/pager.inc b/core/includes/pager.inc
index 91532e3..c1f87c1 100644
--- a/core/includes/pager.inc
+++ b/core/includes/pager.inc
@@ -237,14 +237,14 @@ function theme_pager($variables) {
   if ($pager_total[$element] > 1) {
     if ($li_first) {
       $items[] = array(
-        'class' => array('pager-first'),
-        'data' => $li_first,
+        '#wrapper_attributes' => array('class' => array('pager-first')),
+        '#markup' => $li_first,
       );
     }
     if ($li_previous) {
       $items[] = array(
-        'class' => array('pager-previous'),
-        'data' => $li_previous,
+        '#wrapper_attributes' => array('class' => array('pager-previous')),
+        '#markup' => $li_previous,
       );
     }
 
@@ -252,16 +252,16 @@ function theme_pager($variables) {
     if ($i != $pager_max) {
       if ($i > 1) {
         $items[] = array(
-          'class' => array('pager-ellipsis'),
-          'data' => '…',
+          '#wrapper_attributes' => array('class' => array('pager-ellipsis')),
+          '#markup' => '…',
         );
       }
       // Now generate the actual pager piece.
       for (; $i <= $pager_last && $i <= $pager_max; $i++) {
         if ($i < $pager_current) {
           $items[] = array(
-            'class' => array('pager-item'),
-            'data' => theme('pager_link', array(
+            '#wrapper_attributes' => array('class' => array('pager-item')),
+            '#markup' => theme('pager_link', array(
               'text' => $i,
               'page_new' => pager_load_array($i - 1, $element, $pager_page_array),
               'element' => $element,
@@ -272,14 +272,14 @@ function theme_pager($variables) {
         }
         if ($i == $pager_current) {
           $items[] = array(
-            'class' => array('pager-current'),
-            'data' => $i,
+            '#wrapper_attributes' => array('class' => array('pager-current')),
+            '#markup' => $i,
           );
         }
         if ($i > $pager_current) {
           $items[] = array(
-            'class' => array('pager-item'),
-            'data' => theme('pager_link', array(
+            '#wrapper_attributes' => array('class' => array('pager-item')),
+            '#markup' => theme('pager_link', array(
               'text' => $i,
               'page_new' => pager_load_array($i - 1, $element, $pager_page_array),
               'element' => $element,
@@ -291,22 +291,22 @@ function theme_pager($variables) {
       }
       if ($i < $pager_max) {
         $items[] = array(
-          'class' => array('pager-ellipsis'),
-          'data' => '…',
+          '#wrapper_attributes' => array('class' => array('pager-ellipsis')),
+          '#markup' => '…',
         );
       }
     }
     // End generation.
     if ($li_next) {
       $items[] = array(
-        'class' => array('pager-next'),
-        'data' => $li_next,
+        '#wrapper_attributes' => array('class' => array('pager-next')),
+        '#markup' => $li_next,
       );
     }
     if ($li_last) {
       $items[] = array(
-        'class' => array('pager-last'),
-        'data' => $li_last,
+        '#wrapper_attributes' => array('class' => array('pager-last')),
+        '#markup' => $li_last,
       );
     }
     return '<h2 class="element-invisible">' . t('Pages') . '</h2>' . theme('item_list', array(
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index cc29a98..e68d58f 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -909,6 +909,9 @@ function theme($hook, $variables = array()) {
     }
     $hook = $candidate;
   }
+  // Save the original theme hook, so it can be supplied to theme variable
+  // preprocess callbacks.
+  $original_hook = $hook;
 
   // If there's no implementation, check for more generic fallbacks. If there's
   // still no implementation, log an error and return an empty string.
@@ -968,6 +971,10 @@ function theme($hook, $variables = array()) {
   elseif (!empty($info['render element'])) {
     $variables += array($info['render element'] => array());
   }
+  // Supply original caller info.
+  $variables += array(
+    'theme_hook_original' => $original_hook,
+  );
 
   // Invoke the variable processors, if any. The processors may specify
   // alternate suggestions for which hook's template/function to use. If the
@@ -2074,19 +2081,60 @@ function theme_mark($variables) {
 }
 
 /**
+ * Preprocesses variables for theme_item_list().
+ *
+ * @param array $variables
+ *   An associative array containing theme variables for theme_item_list().
+ *   'items' in variables will be processed to automatically inherit the
+ *   variables of this list to any possibly contained nested lists that do not
+ *   specify custom render properties. This allows callers to specify larger
+ *   nested lists, without having to explicitly specify and repeat the render
+ *   properties for all nested child lists.
+ */
+function template_preprocess_item_list(&$variables) {
+  foreach ($variables['items'] as &$item) {
+    // If the item value is an array, then it is a render array.
+    if (is_array($item)) {
+      // Determine whether there are any child elements in the item that are not
+      // fully-specified render arrays. If there are any, then the child
+      // elements present nested lists and we automatically inherit the render
+      // array properties of the current list to them.
+      foreach (element_children($item) as $key) {
+        $child = &$item[$key];
+        // If this child element does not specify how it can be rendered, then
+        // we need to inherit the render properties of the current list.
+        if (!isset($child['#type']) && !isset($child['#theme']) && !isset($child['#markup'])) {
+          // Since theme_item_list() supports both strings and render arrays as
+          // items, the items of the nested list may have been specified as the
+          // child elements of the nested list, instead of #items. For
+          // convenience, we automatically move them into #items.
+          if (!isset($child['#items'])) {
+            // This is the same condition as in element_children(), which cannot
+            // be used here, since it triggers an error on string values.
+            foreach ($child as $child_key => $child_value) {
+              if ($child_key[0] !== '#') {
+                $child['#items'][$child_key] = $child_value;
+                unset($child[$child_key]);
+              }
+            }
+          }
+          // Lastly, inherit the original theme variables of the current list.
+          $child['#theme'] = $variables['theme_hook_original'];
+          $child['#type'] = $variables['type'];
+        }
+      }
+    }
+  }
+}
+
+/**
  * Returns HTML for a list or nested list of items.
  *
  * @param $variables
  *   An associative array containing:
- *   - items: A list of items to render. String values are rendered as is. Each
- *     item can also be an associative array containing:
- *     - data: The string content of the list item.
- *     - children: A list of nested child items to render that behave
- *       identically to 'items', but any non-numeric string keys are treated as
- *       HTML attributes for the child list that wraps 'children'.
- *     - type: The type of list to return (e.g. "ul", "ol").
- *     Any other key/value pairs are used as HTML attributes for the list item
- *     in 'data'.
+ *   - items: A list of items to render. Allowed values are strings or
+ *     render arrays. In case of a render array, the #wrapper_attributes
+ *     property can be used to specify attributes for the wrapping LI tag.
  *   - title: The title of the list.
  *   - type: The type of list to return (e.g. "ul", "ol").
  *   - attributes: The attributes applied to the list element.
@@ -2094,6 +2142,7 @@ function theme_mark($variables) {
 function theme_item_list($variables) {
   $items = $variables['items'];
   $title = (string) $variables['title'];
+  // @todo 'type' clashes with '#type'. Rename to 'tag'.
   $type = $variables['type'];
   $list_attributes = $variables['attributes'];
 
@@ -2103,40 +2152,15 @@ function theme_item_list($variables) {
 
     $num_items = count($items);
     $i = 0;
-    foreach ($items as $key => $item) {
+    foreach ($items as &$item) {
       $i++;
       $attributes = array();
-
       if (is_array($item)) {
-        $value = '';
-        if (isset($item['data'])) {
-          $value .= $item['data'];
-        }
-        $attributes = array_diff_key($item, array('data' => 0, 'children' => 0, 'type' => 0));
-
-        // Append nested child list, if any.
-        if (isset($item['children'])) {
-          // HTML attributes for the outer list are defined in the 'attributes'
-          // theme variable, but not inherited by children. For nested lists,
-          // all non-numeric keys in 'children' are used as list attributes.
-          $child_list_attributes = array();
-          foreach ($item['children'] as $child_key => $child_item) {
-            if (is_string($child_key)) {
-              $child_list_attributes[$child_key] = $child_item;
-              unset($item['children'][$child_key]);
-            }
-          }
-          $value .= theme('item_list', array(
-            'items' => $item['children'],
-            'type' => (isset($item['type']) ? $item['type'] : $type),
-            'attributes' => $child_list_attributes,
-          ));
+        if (isset($item['#wrapper_attributes'])) {
+          $attributes = $item['#wrapper_attributes'];
         }
+        $item = drupal_render($item);
       }
-      else {
-        $value = $item;
-      }
-
       $attributes['class'][] = ($i % 2 ? 'odd' : 'even');
       if ($i == 1) {
         $attributes['class'][] = 'first';
@@ -2144,8 +2168,7 @@ function theme_item_list($variables) {
       if ($i == $num_items) {
         $attributes['class'][] = 'last';
       }
-
-      $output .= '<li' . new Attribute($attributes) . '>' . $value . '</li>';
+      $output .= '<li' . new Attribute($attributes) . '>' . $item . '</li>';
     }
     $output .= "</$type>";
   }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/FunctionsTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/FunctionsTest.php
index d0dae7d..d3a090f 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Theme/FunctionsTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/FunctionsTest.php
@@ -43,39 +43,82 @@ function testItemList() {
       'id' => 'parentlist',
     );
     $variables['items'] = array(
+      // A plain string value forms an own item.
       'a',
+      // Items can be fully-fledged render arrays with their own attributes.
       array(
-        'data' => 'b',
-        'children' => array(
-          'c',
-          // Nested children may use additional attributes.
+        '#wrapper_attributes' => array(
+          'id' => 'item-id-b',
+        ),
+        '#markup' => 'b',
+        'childlist' => array(
+          '#theme' => 'item_list',
+          '#attributes' => array('id' => 'blist'),
+          '#type' => 'ol',
+          '#items' => array(
+            'ba',
+            array(
+              '#markup' => 'bb',
+              '#wrapper_attributes' => array('class' => array('item-class-bb')),
+            ),
+          ),
+        ),
+      ),
+      // However, items can also be child #items.
+      array(
+        '#markup' => 'c',
+        'childlist' => array(
+          '#attributes' => array('id' => 'clist'),
+          'ca',
           array(
-            'data' => 'd',
-            'class' => array('dee'),
+            '#markup' => 'cb',
+            '#wrapper_attributes' => array('class' => array('item-class-cb')),
+            'children' => array(
+              'cba',
+              'cbb',
+            ),
           ),
-          // Any string key is treated as child list attribute.
-          'id' => 'childlist',
+          'cc',
         ),
-        // Any other keys are treated as item attributes.
-        'id' => 'bee',
-        'type' => 'ol',
       ),
+      // Use #markup to be able to specify #wrapper_attributes.
       array(
-        'data' => 'e',
-        'id' => 'E',
+        '#markup' => 'd',
+        '#wrapper_attributes' => array('id' => 'item-id-d'),
       ),
+      // An empty item with attributes.
+      array(
+        '#wrapper_attributes' => array('id' => 'item-id-e'),
+      ),
+      // Lastly, another plain string item.
+      'f',
     );
-    $inner = '<div class="item-list"><ol id="childlist">';
-    $inner .= '<li class="odd first">c</li>';
-    $inner .= '<li class="dee even last">d</li>';
-    $inner .= '</ol></div>';
+
+    $inner_b = '<div class="item-list"><ol id="blist">';
+    $inner_b .= '<li class="odd first">ba</li>';
+    $inner_b .= '<li class="item-class-bb even last">bb</li>';
+    $inner_b .= '</ol></div>';
+
+    $inner_cb = '<div class="item-list"><ul>';
+    $inner_cb .= '<li class="odd first">cba</li>';
+    $inner_cb .= '<li class="even last">cbb</li>';
+    $inner_cb .= '</ul></div>';
+
+    $inner_c = '<div class="item-list"><ul id="clist">';
+    $inner_c .= '<li class="odd first">ca</li>';
+    $inner_c .= '<li class="item-class-cb even">cb' . $inner_cb . '</li>';
+    $inner_c .= '<li class="odd last">cc</li>';
+    $inner_c .= '</ul></div>';
 
     $expected = '<div class="item-list">';
     $expected .= '<h3>Some title</h3>';
     $expected .= '<ul id="parentlist">';
     $expected .= '<li class="odd first">a</li>';
-    $expected .= '<li id="bee" class="even">b' . $inner . '</li>';
-    $expected .= '<li id="E" class="odd last">e</li>';
+    $expected .= '<li id="item-id-b" class="even">b' . $inner_b . '</li>';
+    $expected .= '<li class="odd">c' . $inner_c . '</li>';
+    $expected .= '<li id="item-id-d" class="even">d</li>';
+    $expected .= '<li id="item-id-e" class="odd"></li>';
+    $expected .= '<li class="even last">f</li>';
     $expected .= '</ul></div>';
 
     $this->assertThemeOutput('item_list', $variables, $expected);
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 7f9f33a..fae2a61 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -504,7 +504,6 @@ function system_element_info() {
   // Form structure.
   $types['item'] = array(
     '#markup' => '',
-    '#pre_render' => array('drupal_pre_render_markup'),
     '#theme_wrappers' => array('form_element'),
   );
   $types['hidden'] = array(
@@ -517,10 +516,9 @@ function system_element_info() {
   );
   $types['markup'] = array(
     '#markup' => '',
-    '#pre_render' => array('drupal_pre_render_markup'),
   );
   $types['link'] = array(
-    '#pre_render' => array('drupal_pre_render_link', 'drupal_pre_render_markup'),
+    '#pre_render' => array('drupal_pre_render_link'),
   );
   $types['fieldset'] = array(
     '#collapsible' => FALSE,
@@ -4172,7 +4170,7 @@ function theme_exposed_filters($variables) {
   if (isset($form['current'])) {
     $items = array();
     foreach (element_children($form['current']) as $key) {
-      $items[] = drupal_render($form['current'][$key]);
+      $items[] = $form['current'][$key];
     }
     $output .= theme('item_list', array('items' => $items, 'attributes' => array('class' => array('clearfix', 'current-filters'))));
   }
