diff --git a/includes/common.inc b/includes/common.inc
index e33a5c187c035625d8bb4ab2ec1bc4cadc699496..dd7e58c3582ace0076a4878cd4081564f3de9a80 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -301,8 +301,7 @@ function drupal_get_destination() {
  * Drupal will ensure that messages set by drupal_set_message() and other
  * session data are written to the database before the user is redirected.
  *
- * This function ends the request; use it rather than a print theme('page')
- * statement in your menu callback.
+ * This function ends the request; use it instead of a return in your menu callback.
  *
  * @param $path
  *   A Drupal path or a full URL.
@@ -379,19 +378,23 @@ function drupal_not_found() {
 
   $path = drupal_get_normal_path(variable_get('site_404', ''));
   if ($path && $path != $_GET['q']) {
-    // Set the active item in case there are tabs to display, or other
-    // dependencies on the path.
+    // Custom 404 handler. Set the active item in case there are tabs to
+    // display, or other dependencies on the path.
     menu_set_active_item($path);
     $return = menu_execute_active_handler($path);
   }
 
   if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
+    // Standard 404 handler.
     drupal_set_title(t('Page not found'));
     $return = t('The requested page could not be found.');
   }
 
+  $page = drupal_get_page($return);
   // To conserve CPU and bandwidth, omit the blocks.
-  print theme('page', $return, FALSE);
+  $page['#show_blocks'] = FALSE;
+
+  print drupal_render_page($page);
 }
 
 /**
@@ -408,17 +411,19 @@ function drupal_access_denied() {
 
   $path = drupal_get_normal_path(variable_get('site_403', ''));
   if ($path && $path != $_GET['q']) {
-    // Set the active item in case there are tabs to display or other
-    // dependencies on the path.
+    // Custom 403 handler. Set the active item in case there are tabs to
+    // display or other dependencies on the path.
     menu_set_active_item($path);
     $return = menu_execute_active_handler($path);
   }
 
   if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
+    // Standard 403 handler.
     drupal_set_title(t('Access denied'));
     $return = t('You are not authorized to access this page.');
   }
-  print theme('page', $return);
+
+  print drupal_render_page($return);
 }
 
 /**
@@ -816,7 +821,10 @@ function _drupal_log_error($error, $fatal = FALSE) {
     drupal_set_header($_SERVER['SERVER_PROTOCOL'] . ' Service unavailable');
     drupal_set_title(t('Error'));
     if (!defined('MAINTENANCE_MODE') && drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL) {
-      print theme('page', t('The website encountered an unexpected error. Please try again later.'), FALSE);
+      // To conserve CPU and bandwidth, omit the blocks.
+      $page = drupal_get_page(t('The website encountered an unexpected error. Please try again later.'));
+      $page['#show_blocks'] = FALSE;
+      print drupal_render_page($page);
     }
     else {
       print theme('maintenance_page', t('The website encountered an unexpected error. Please try again later.'), FALSE);
@@ -3194,6 +3202,53 @@ function drupal_alter($type, &$data) {
   }
 }
 
+/**
+ * Retrieve a $page element that is ready for decorating.
+ *
+ * Used by menu callbacks in order to populate the page with content
+ * and behavior (e.g. #show_blocks).
+ *
+ * @param $content
+ *   A string or renderable array representing the body of the page.
+ * @return
+ *   A $page element that should be decorated and then passed to drupal_render_page().
+ *
+ * @see drupal_render_page().
+ */
+function drupal_get_page($content = NULL) {
+  // Initialize page array with defaults. @see hook_elements() - 'page' element.
+  $page = _element_info('page');
+  $page['content'] = is_array($content) ? $content : array('main' => array('#markup' => $content));
+
+  return $page;
+}
+
+/**
+ * Renders the page, including all theming.
+ *
+ * @param $page
+ *   A string or array representing the content of a page. The array consists of
+ *   the following keys:
+ *   - #type: Value is always 'page'. This pushes the theming through page.tpl.php (required).
+ *   - content: A renderable array as built by the menu callback (required).
+ *   - #show_blocks: A marker which suppresses left/right regions if FALSE (optional).
+ *   - #show_messages: Suppress drupal_get_message() items. Used by Batch API (optional).
+ *
+ * @see hook_page_alter()
+ * @see drupal_get_page()
+ */
+function drupal_render_page($page) {
+  // Allow menu callbacks to return strings.
+  if (is_string($page)) {
+    $page = drupal_get_page($page);
+  }
+  // Modules alter the $page as needed. Blocks are populated into regions like
+  // 'left', 'footer', etc.
+  drupal_alter('page', $page);
+
+  return drupal_render($page);
+}
+
 /**
  * Renders HTML given a structured array tree.
  *
@@ -3366,7 +3421,7 @@ function drupal_common_theme() {
       'arguments' => array('text' => NULL)
     ),
     'page' => array(
-      'arguments' => array('content' => NULL, 'show_blocks' => TRUE, 'show_messages' => TRUE),
+      'arguments' => array('page' => NULL),
       'template' => 'page',
     ),
     'maintenance_page' => array(
@@ -3425,6 +3480,9 @@ function drupal_common_theme() {
     'item_list' => array(
       'arguments' => array('items' => array(), 'title' => NULL, 'type' => 'ul', 'attributes' => NULL),
     ),
+    'list' => array(
+      'arguments' => array('elements' => NULL),
+    ),
     'more_help_link' => array(
       'arguments' => array('url' => NULL),
     ),
diff --git a/includes/theme.inc b/includes/theme.inc
index b71da908bffe2901812f716ef759681ae9b0d3aa..b77e35854245313008188923cff97399b31af253 100644
--- a/includes/theme.inc
+++ b/includes/theme.inc
@@ -1568,6 +1568,25 @@ function theme_item_list($items = array(), $title = NULL, $type = 'ul', $attribu
   return $output;
 }
 
+/**
+ * Return a themed list of items from a drupal_render() style array.
+ *
+ * @param $elements
+ *   An array consisting of the following keys:
+ *   - #items: an array of items as expected by theme('item_list').
+ *   - #title: a title which prints above the list.
+ *   - #list_type: the type of list to return. Defaults to "ul".
+ *   - #attributes: an array of attributes as expected by theme('item_list').
+ * @return
+ *   A string containing the list output.
+ */
+function theme_list($elements) {
+  // Populate any missing array elements with their defaults.
+  $elements += _element_info('list');
+
+  return theme('item_list', $elements['#items'], $elements['#title'], $elements['#list_type'], $elements['#attributes']);
+}
+
 /**
  * Returns code that emits the 'more help'-link.
  */
@@ -1630,30 +1649,6 @@ function theme_closure($main = 0) {
   return implode("\n", $footer) . drupal_get_js('footer');
 }
 
-/**
- * Return a set of blocks available for the current user.
- *
- * @param $region
- *   Which set of blocks to retrieve.
- * @return
- *   A string containing the themed blocks for this region.
- */
-function theme_blocks($region) {
-  $output = '';
-
-  if ($list = block_list($region)) {
-    foreach ($list as $key => $block) {
-      // $key == <i>module</i>_<i>delta</i>
-      $output .= theme('block', $block);
-    }
-  }
-
-  // Add any content assigned to this region through drupal_set_content() calls.
-  $output .= drupal_get_content($region);
-
-  return $output;
-}
-
 /**
  * Format a username.
  *
@@ -1816,32 +1811,26 @@ function template_preprocess(&$variables, $hook) {
  * Any changes to variables in this preprocessor should also be changed inside
  * template_preprocess_maintenance_page() to keep all of them consistent.
  *
- * The $variables array contains the following arguments:
- * - $content
- * - $show_blocks
+ * The $variables array contains two keys:
+ * - 'page': the fully decorated page.
+ * - 'content': the content of the page, already rendered.
  *
+ * @see drupal_render_page
  * @see page.tpl.php
  */
 function template_preprocess_page(&$variables) {
-  // Add favicon
-  if (theme_get_setting('toggle_favicon')) {
-    drupal_set_html_head('<link rel="shortcut icon" href="' . check_url(theme_get_setting('favicon')) . '" type="image/x-icon" />');
+  // Move some variables to the top level for themer convenience and template cleanliness.
+  $variables['show_blocks'] = $variables['page']['#show_blocks'];
+  $variables['show_messages'] = $variables['page']['#show_messages'];
+
+  // Render each region into top level variables.
+  foreach (system_region_list($GLOBALS['theme']) as $region_key => $region_name) {
+    $variables[$region_key] = empty($variables['page'][$region_key]) ? '' : drupal_render($variables['page'][$region_key]);
   }
 
-  global $theme;
-  // Populate all block regions.
-  $regions = system_region_list($theme);
-  // Load all region content assigned via blocks.
-  foreach (array_keys($regions) as $region) {
-    // Prevent left and right regions from rendering blocks when 'show_blocks' == FALSE.
-    if ($variables['show_blocks'] || ($region != 'left' && $region != 'right')) {
-      $blocks = theme('blocks', $region);
-    }
-    else {
-      $blocks = '';
-    }
-    // Assign region to a region variable.
-    isset($variables[$region]) ? $variables[$region] .= $blocks : $variables[$region] = $blocks;
+  // Add favicon.
+  if (theme_get_setting('toggle_favicon')) {
+    drupal_set_html_head('<link rel="shortcut icon" href="' . check_url(theme_get_setting('favicon')) . '" type="image/x-icon" />');
   }
 
   // Set up layout variable.
@@ -1881,8 +1870,8 @@ function template_preprocess_page(&$variables) {
   $variables['logo']              = theme_get_setting('logo');
   $variables['messages']          = $variables['show_messages'] ? theme('status_messages') : '';
   $variables['mission']           = isset($mission) ? $mission : '';
-  $variables['main_menu']     = theme_get_setting('toggle_main_menu') ? menu_main_menu() : array();
-  $variables['secondary_menu']   = theme_get_setting('toggle_secondary_menu') ? menu_secondary_menu() : array();
+  $variables['main_menu']         = theme_get_setting('toggle_main_menu') ? menu_main_menu() : array();
+  $variables['secondary_menu']    = theme_get_setting('toggle_secondary_menu') ? menu_secondary_menu() : array();
   $variables['search_box']        = (theme_get_setting('toggle_search') ? drupal_get_form('search_theme_form') : '');
   $variables['site_name']         = (theme_get_setting('toggle_name') ? variable_get('site_name', 'Drupal') : '');
   $variables['site_slogan']       = (theme_get_setting('toggle_slogan') ? variable_get('site_slogan', '') : '');
@@ -1975,6 +1964,8 @@ function template_preprocess_page(&$variables) {
  * @see node.tpl.php
  */
 function template_preprocess_node(&$variables) {
+  $variables['teaser'] = $variables['elements']['#teaser'];
+  $variables['node'] = $variables['elements']['#node'];
   $node = $variables['node'];
 
   $variables['date']      = format_date($node->created);
@@ -1982,17 +1973,17 @@ function template_preprocess_node(&$variables) {
   $variables['node_url']  = url('node/' . $node->nid);
   $variables['title']     = check_plain($node->title);
   $variables['page']      = (bool)menu_get_object();
-
+  
   if ($node->build_mode == NODE_BUILD_PREVIEW) {
     unset($node->content['links']);
   }
-
+  
   // Render taxonomy links separately.
   $variables['terms']     = !empty($node->content['links']['terms']) ? drupal_render($node->content['links']['terms']) : '';
-
+  
   // Render all remaining node links.
   $variables['links']     = !empty($node->content['links']) ? drupal_render($node->content['links']) : '';
-
+  
   // Render any comments.
   $variables['comments']  = !empty($node->content['comments']) ? drupal_render($node->content['comments']) : '';
 
@@ -2035,6 +2026,7 @@ function template_preprocess_node(&$variables) {
  */
 function template_preprocess_block(&$variables) {
   static $block_counter = array();
+  $variables['block'] = $variables['block']['#block'];
   // All blocks get an independent counter for each region.
   if (!isset($block_counter[$variables['block']->region])) {
     $block_counter[$variables['block']->region] = 1;
diff --git a/index.php b/index.php
index dba9f1123c9bcc7067c59f5856f99866dab5255e..d776d767a7574293d52d7cb1203b67ec9f65e7ea 100644
--- a/index.php
+++ b/index.php
@@ -21,7 +21,7 @@
 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
 $return = menu_execute_active_handler();
 
-// Menu status constants are integers; page content is a string.
+// Menu status constants are integers; page content is a string or array.
 if (is_int($return)) {
   switch ($return) {
     case MENU_NOT_FOUND:
@@ -36,8 +36,8 @@
   }
 }
 elseif (isset($return)) {
-  // Print any value (including an empty string) except NULL or undefined:
-  print theme('page', $return);
+  // Print anything besides a menu constant, assuming it's not NULL or undefined.
+  print drupal_render_page($return);
 }
 
 drupal_page_footer();
diff --git a/modules/block/block.admin.inc b/modules/block/block.admin.inc
index ead47fba0c889a3aec03a7b6e3ef40561f50edda..00afc9bd82a038e85a0d03783db1344814f48f2c 100644
--- a/modules/block/block.admin.inc
+++ b/modules/block/block.admin.inc
@@ -368,8 +368,6 @@ function template_preprocess_block_admin_display_form(&$variables) {
   $variables['block_regions'] = $block_regions + array(BLOCK_REGION_NONE => t('Disabled'));
 
   foreach ($block_regions as $key => $value) {
-    // Highlight regions on page to provide visual reference.
-    drupal_set_content($key, '<div class="block-region">' . $value . '</div>');
     // Initialize an empty array for the region.
     $variables['block_listing'][$key] = array();
   }
diff --git a/modules/block/block.module b/modules/block/block.module
index 318617e8bfe47a83561fd4e446e3c979512cecd4..c08bac949874e8043c3e308a03c8659440437987 100644
--- a/modules/block/block.module
+++ b/modules/block/block.module
@@ -225,6 +225,61 @@ function block_block_view($delta = 0, $edit = array()) {
   return $data;
 }
 
+/**
+ * Implementation of hook_page_alter().
+ *
+ * Render blocks into their regions.
+ */
+function block_page_alter($page) {
+  global $theme;
+
+  // The theme system might not yet be initialized. We need $theme.
+  init_theme();
+
+  // Populate all block regions
+  $regions = system_region_list($theme);
+
+  // Load all region content assigned via blocks.
+  foreach (array_keys($regions) as $region) {
+    // Prevent left and right regions from rendering blocks when 'show_blocks' == FALSE.
+    if ($page['#show_blocks'] || ($region != 'left' && $region != 'right')) {
+      // Assign blocks to region.
+      if ($blocks = block_get_blocks_by_region($region)) {
+        $page[$region]['blocks'] = $blocks;
+      }
+
+      // Append region description if we are rendering the block admin page.
+      $item = menu_get_item();
+      if ($item['path'] == 'admin/build/block') {
+        $description = '<div class="block-region">' . $regions[$region] . '</div>';
+        $page[$region]['blocks']['block_description'] = array('#markup' => $description);
+      }
+    }
+  }
+}
+
+/**
+ * Get a renderable array of a region containing all enabled blocks.
+ *
+ * @param $region
+ *   The requested region.
+ */
+function block_get_blocks_by_region($region) {
+  $weight = 0;
+  $build = array();
+  if ($list = block_list($region)) {
+    foreach ($list as $key => $block) {
+      $build[$key] = array(
+        '#theme' => 'block',
+        '#block' => $block,
+        '#weight' => ++$weight,
+      );
+    }
+    $build['#sorted'] = TRUE;
+  }
+  return $build;
+}
+
 /**
  * Update the 'block' DB table with the blocks currently exported by modules.
  *
diff --git a/modules/blog/blog.pages.inc b/modules/blog/blog.pages.inc
index d44bbf5fa42bfce5991e4635c2f3f093826568d2..889bf47a166ce7ebc1b77bc2ee9137e861864b8c 100644
--- a/modules/blog/blog.pages.inc
+++ b/modules/blog/blog.pages.inc
@@ -23,18 +23,20 @@ function blog_page_user($account) {
     $items[] = t('You are not allowed to post a new blog entry.');
   }
 
-  $output = theme('item_list', $items);
-
-  $result = pager_query(db_rewrite_sql("SELECT n.nid, n.sticky, n.created FROM {node} n WHERE n.type = 'blog' AND n.uid = %d AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC"), variable_get('default_nodes_main', 10), 0, NULL, $account->uid);
-  $has_posts = FALSE;
-
-  while ($node = db_fetch_object($result)) {
-    $output .= node_view(node_load($node->nid), 1);
-    $has_posts = TRUE;
-  }
-
-  if ($has_posts) {
-    $output .= theme('pager', NULL, variable_get('default_nodes_main', 10));
+  $build['blog_actions'] = array(
+    '#items' => $items,
+    '#theme' => 'list',
+    '#weight' => -1,
+  );
+
+  $nids = pager_query(db_rewrite_sql("SELECT n.nid, n.sticky, n.created FROM {node} n WHERE n.type = 'blog' AND n.uid = %d AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC"), variable_get('default_nodes_main', 10), 0, NULL, $account->uid)->fetchCol();
+  if (!empty($nids)) {
+    $nodes = node_load_multiple($nids);
+    $build += node_build_multiple($nodes);
+    $build['pager'] = array(
+      '#markup' => theme('pager', NULL, variable_get('default_nodes_main', 10)),
+      '#weight' => 5,
+    );
   }
   else {
     if ($account->uid == $user->uid) {
@@ -46,7 +48,7 @@ function blog_page_user($account) {
   }
   drupal_add_feed(url('blog/' . $account->uid . '/feed'), t('RSS - !title', array('!title' => $title)));
 
-  return $output;
+  return drupal_get_page($build);
 }
 
 /**
@@ -54,33 +56,33 @@ function blog_page_user($account) {
  */
 function blog_page_last() {
   global $user;
-
-  $output = '';
-  $items = array();
+  $build = array();
 
   if (user_access('edit own blog')) {
     $items[] = l(t('Create new blog entry.'), "node/add/blog");
+    $build['blog_actions'] = array(
+      '#items' => $items,
+      '#theme' => 'list',
+      '#weight' => -1,
+    );
   }
 
-  $output = theme('item_list', $items);
-
-  $result = pager_query(db_rewrite_sql("SELECT n.nid, n.created FROM {node} n WHERE n.type = 'blog' AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC"), variable_get('default_nodes_main', 10));
-  $has_posts = FALSE;
-
-  while ($node = db_fetch_object($result)) {
-    $output .= node_view(node_load($node->nid), 1);
-    $has_posts = TRUE;
-  }
+  $nids = pager_query(db_rewrite_sql("SELECT n.nid, n.sticky, n.created FROM {node} n WHERE n.type = 'blog' AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC"), variable_get('default_nodes_main', 10))->fetchCol();
 
-  if ($has_posts) {
-    $output .= theme('pager', NULL, variable_get('default_nodes_main', 10));
+  if (!empty($nids)) {
+    $nodes = node_load_multiple($nids);
+    $build += node_build_multiple($nodes);
+    $build['pager'] = array(
+      '#markup' => theme('pager', NULL, variable_get('default_nodes_main', 10)),
+      '#weight' => 5,
+    );
   }
   else {
     drupal_set_message(t('No blog entries have been created.'));
   }
   drupal_add_feed(url('blog/feed'), t('RSS - blogs'));
 
-  return $output;
+  return drupal_get_page($build);
 }
 
 /**
diff --git a/modules/comment/comment.module b/modules/comment/comment.module
index b1e48ed3ac9dda12f2f1d8cf9a2dd63fe335b59a..a92b96a455a1aa3434ccb254827c491ad4a82ed3 100644
--- a/modules/comment/comment.module
+++ b/modules/comment/comment.module
@@ -1649,7 +1649,7 @@ function comment_form_add_preview($form, &$form_state) {
   }
   else {
     $suffix = empty($form['#suffix']) ? '' : $form['#suffix'];
-    $form['#suffix'] = $suffix . node_view($node);
+    $form['#suffix'] = $suffix . drupal_render(node_build($node));
     $edit['pid'] = 0;
   }
 
diff --git a/modules/comment/comment.pages.inc b/modules/comment/comment.pages.inc
index a4c94e22ae1a263ac14e13eda60d4bc3d99be54c..12d30cec8fcf82d1fbf265b11553bc85577ee299 100644
--- a/modules/comment/comment.pages.inc
+++ b/modules/comment/comment.pages.inc
@@ -92,7 +92,7 @@ function comment_reply($node, $pid = NULL) {
       }
       // This is the case where the comment is in response to a node. Display the node.
       elseif (user_access('access content')) {
-        $output .= node_view($node);
+        $output .= drupal_render(node_build($node));
       }
 
       // Should we show the reply box?
diff --git a/modules/node/node.api.php b/modules/node/node.api.php
index 68050065d1233c531aa5b776df8946477e388759..8b8343d2ed0f6abb7d96f8c85f436dd83afe03a0 100644
--- a/modules/node/node.api.php
+++ b/modules/node/node.api.php
@@ -81,7 +81,7 @@ function hook_node_access_records($node) {
     return;
   }
 
-  // We only care about the node if it's been marked private. If not, it is
+  // We only care about the node if it has been marked private. If not, it is
   // treated just like any other node and we completely ignore it.
   if ($node->private) {
     $grants = array();
@@ -169,7 +169,7 @@ function hook_node_operations() {
  * @return
  *   None.
  */
-function hook_nodeapi_alter($node, $teaser, $page) {
+function hook_nodeapi_alter($node, $teaser) {
 }
 
 /**
diff --git a/modules/node/node.module b/modules/node/node.module
index 3936359ade75ace8ca902b1c421750ef8d8a9ebd..3629e2209c637f9ad9678ccb693dff66d2d04d9d 100644
--- a/modules/node/node.module
+++ b/modules/node/node.module
@@ -96,7 +96,7 @@ function node_help($path, $arg) {
 function node_theme() {
   return array(
     'node' => array(
-      'arguments' => array('node' => NULL, 'teaser' => FALSE, 'page' => FALSE),
+      'arguments' => array('elements' => NULL),
       'template' => 'node',
     ),
     'node_list' => array(
@@ -1126,24 +1126,28 @@ function node_delete($nid) {
 }
 
 /**
- * Generate a display of the given node.
+ * Generate an array for rendering the given node.
  *
  * @param $node
  *   A node array or node object.
  * @param $teaser
  *   Whether to display the teaser only or the full form.
- * @param $links
- *   Whether or not to display node links. Links are omitted for node previews.
  *
  * @return
- *   An HTML representation of the themed node.
+ *   An array as expected by drupal_render().
  */
-function node_view($node, $teaser = FALSE) {
+function node_build($node, $teaser = FALSE) {
   $node = (object)$node;
 
   $node = node_build_content($node, $teaser);
 
-  return theme('node', $node, $teaser);
+  $build = $node->content;
+  $build += array(
+    '#theme' => 'node',
+    '#node' => $node,
+    '#teaser' => $teaser,
+  );
+  return $build;
 }
 
 /**
@@ -1205,25 +1209,31 @@ function node_build_content($node, $teaser = FALSE) {
   node_invoke_nodeapi($node, 'view', $teaser);
 
   // Allow modules to modify the structured node.
-  drupal_alter('node_view', $node, $teaser);
+  drupal_alter('node_build', $node, $teaser);
 
   return $node;
 }
 
 /**
- * Generate a page displaying a single node.
+ * Generate an array which displays a node detail page.
+ *
+ * @param $node
+ *   A node object.
+ * @param $message
+ *   A flag which sets a page title relevant to the revision being viewed.
+ * @return
+ *   A $page element suitable for use by drupal_page_render().
  */
 function node_show($node, $message = FALSE) {
   if ($message) {
     drupal_set_title(t('Revision of %title from %date', array('%title' => $node->title, '%date' => format_date($node->revision_timestamp))), PASS_THROUGH);
   }
 
-  $output = node_view($node, FALSE, TRUE);
-
   // Update the history table, stating that this user viewed this node.
   node_tag_new($node->nid);
 
-  return $output;
+  // For markup consistency with other pages, use node_build_multiple() rather than node_build().
+  return drupal_get_page(node_build_multiple(array($node), FALSE));
 }
 
 /**
@@ -1904,21 +1914,44 @@ function node_feed($nids = FALSE, $channel = array()) {
   print $output;
 }
 
+/**
+ * Construct a drupal_render() style array from an array of loaded nodes.
+ *
+ * @param $nodes
+ *   An array of nodes as returned by node_load_multiple().
+ * @param $teaser
+ *   Display nodes into teaser view or full view.
+ * @param $weight
+ *   An integer representing the weight of the first node in the list.
+ * @return
+ *   An array in the format expected by drupal_render().
+ */
+function node_build_multiple($nodes, $teaser = TRUE, $weight = 0) {
+  $build = array();
+  foreach ($nodes as $node) {
+    $build['nodes'][$node->nid] = node_build($node, $teaser);
+    $build['nodes'][$node->nid]['#weight'] = $weight;
+    $weight++;
+  }
+  $build['nodes']['#sorted'] = TRUE;
+  return $build;
+}
+
 /**
  * Menu callback; Generate a listing of promoted nodes.
  */
 function node_page_default() {
-  $nids = pager_query(db_rewrite_sql('SELECT n.nid FROM {node} n WHERE n.promote = 1 AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC'), variable_get('default_nodes_main', 10))->fetchCol();
+  $nids = pager_query(db_rewrite_sql('SELECT n.nid, n.sticky, n.created FROM {node} n WHERE n.promote = 1 AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC'), variable_get('default_nodes_main', 10))->fetchCol();
   if (!empty($nids)) {
     $nodes = node_load_multiple($nids);
-    $output = '';
-    foreach ($nodes as $node) {
-      $output .= node_view($node, TRUE);
-    }
+    $build = node_build_multiple($nodes);
 
     $feed_url = url('rss.xml', array('absolute' => TRUE));
     drupal_add_feed($feed_url, variable_get('site_name', 'Drupal') . ' ' . t('RSS'));
-    $output .= theme('pager', NULL, variable_get('default_nodes_main', 10));
+    $build['pager'] = array(
+      '#markup' => theme('pager', NULL, variable_get('default_nodes_main', 10)),
+      '#weight' => 5,
+    );
   }
   else {
     $default_message = '<h1 class="title">' . t('Welcome to your new Drupal website!') . '</h1>';
@@ -1930,12 +1963,14 @@ function node_page_default() {
     $default_message .= '<li>' . t('<strong>Start posting content</strong> Finally, you can <a href="@content">create content</a> for your website. This message will disappear once you have promoted a post to the front page.', array('@content' => url('node/add'))) . '</li>';
     $default_message .= '</ol>';
     $default_message .= '<p>' . t('For more information, please refer to the <a href="@help">help section</a>, or the <a href="@handbook">online Drupal handbooks</a>. You may also post at the <a href="@forum">Drupal forum</a>, or view the wide range of <a href="@support">other support options</a> available.', array('@help' => url('admin/help'), '@handbook' => 'http://drupal.org/handbooks', '@forum' => 'http://drupal.org/forum', '@support' => 'http://drupal.org/support')) . '</p>';
-
-    $output = '<div id="first-time">' . $default_message . '</div>';
+    $build['default_message'] = array(
+      '#markup' => $default_message,
+      '#prefix' => '<div id="first-time">',
+      '#suffix' => '</div>',
+    );
   }
   drupal_set_title('');
-
-  return $output;
+  return drupal_get_page($build);
 }
 
 /**
@@ -2969,7 +3004,7 @@ function node_unpublish_by_keyword_action_submit($form, $form_state) {
  */
 function node_unpublish_by_keyword_action($node, $context) {
   foreach ($context['keywords'] as $keyword) {
-    if (strstr(node_view(clone $node), $keyword) || strstr($node->title, $keyword)) {
+    if (strstr(drupal_render(node_build(clone $node)), $keyword) || strstr($node->title, $keyword)) {
       $node->status = 0;
       watchdog('action', 'Set @type %title to unpublished.', array('@type' => node_get_types('name', $node), '%title' => $node->title));
       break;
diff --git a/modules/node/node.pages.inc b/modules/node/node.pages.inc
index 28aff234a930f5699b54f82b5c85e7a610ee0625..a6e5d34bfc288bafc15168f71e7805765df09586 100644
--- a/modules/node/node.pages.inc
+++ b/modules/node/node.pages.inc
@@ -415,12 +415,12 @@ function theme_node_preview($node) {
   if ($preview_trimmed_version) {
     drupal_set_message(t('The trimmed version of your post shows what your post looks like when promoted to the main page or when exported for syndication.<span class="no-js"> You can insert the delimiter "&lt;!--break--&gt;" (without the quotes) to fine-tune where your post gets split.</span>'));
     $output .= '<h3>' . t('Preview trimmed version') . '</h3>';
-    $output .= node_view(clone $node, 1, FALSE);
+    $output .= drupal_render(node_build(clone $node, TRUE));
     $output .= '<h3>' . t('Preview full version') . '</h3>';
-    $output .= node_view($node, 0, FALSE);
+    $output .= drupal_render(node_build($node, FALSE));
   }
   else {
-    $output .= node_view($node, 0, FALSE);
+    $output .= drupal_render(node_build($node, FALSE));
   }
   $output .= "</div>\n";
 
diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc
index 703de2d0127c8bf6c96bb9bfdcc1621b23fe90b8..be461d72774910fcece8bb9d57abbc8fb61562ba 100644
--- a/modules/system/system.admin.inc
+++ b/modules/system/system.admin.inc
@@ -1888,7 +1888,10 @@ function system_batch_page() {
   elseif (isset($output)) {
     // Force a page without blocks or messages to
     // display a list of collected messages later.
-    print theme('page', $output, FALSE, FALSE);
+    $page = drupal_get_page($output);
+    $page['#show_blocks'] = FALSE;
+    $page['#show_messages'] = FALSE;
+    return $page;
   }
 }
 
diff --git a/modules/system/system.api.php b/modules/system/system.api.php
index 3c74f48db4b83582b3668e455cf260e9c8d93001..cd98c508150f96d820e48cfaf23bb8e3fe3c728a 100644
--- a/modules/system/system.api.php
+++ b/modules/system/system.api.php
@@ -179,6 +179,63 @@ function hook_js_alter(&$javascript) {
   $javascript['misc/jquery.js']['data'] = drupal_get_path('module', 'jquery_update') . '/jquery.js';
 }
 
+/**
+ * Perform alterations before a page is rendered.
+ *
+ * Use this hook when you want to add, remove, or alter elements at the page
+ * level. If you are making changes to entities such as forms, menus, or user
+ * profiles, use those objects' native alter hooks instead (hook_form_alter(),
+ * for example).
+ *
+ * The $page array contains top level elements for each block region:
+ * @code
+ *   $page['header']
+ *   $page['left']
+ *   $page['content']
+ *   $page['right']
+ *   $page['footer']
+ * @endcode
+ *
+ * The 'content' element contains the main content of the current page, and its
+ * structure will vary depending on what module is responsible for building the
+ * page. Some legacy modules may not return structured content at all: their
+ * pre-rendered markup will be located in $page['content']['main']['#markup'].
+ *
+ * Pages built by Drupal's core Node and Blog modules use a standard structure:
+ *
+ * @code
+ *   // Node body.
+ *   $page['content']['nodes'][$nid]['body']
+ *   // Array of links attached to the node (add comments, read more).
+ *   $page['content']['nodes'][$nid]['links']
+ *   // The node object itself.
+ *   $page['content']['nodes'][$nid]['#node']
+ *   // The results pager.
+ *   $page['content']['pager']
+ * @code 
+ *
+ * Blocks may be referenced by their module/delta pair within a region:
+ * @code
+ *   // The login block in the left sidebar region.
+ *   $page['left']['user-login']['#block'];
+ * @endcode
+ *
+ * @param $page
+ *   Nested array of renderable elements that make up the page.
+ *
+ * @see drupal_render_page()
+ */
+function hook_page_alter($page) {
+  if (menu_get_object('node', 1)) {
+    // We are on a node detail page. Append a standard disclaimer to the
+    // content region.
+    $page['content']['disclaimer'] = array(
+      '#markup' => t('Acme, Inc. is not responsible for the contents of this sample code.'),
+      '#weight' => 25,
+    );
+  }
+}
+
 /**
  * Perform alterations before a form is rendered.
  *
diff --git a/modules/system/system.module b/modules/system/system.module
index f5031825480661d545670632e248569bbc864c24..581ae0aab18558e4d2554eff03cead331d626385 100644
--- a/modules/system/system.module
+++ b/modules/system/system.module
@@ -237,6 +237,19 @@ function system_elements() {
     '#action' => request_uri(),
   );
 
+  $type['page'] = array(
+    '#show_messages' => TRUE,
+    '#show_blocks' => TRUE,
+    '#theme' => 'page',
+  );
+
+  $type['list'] = array(
+    '#title' => '',
+    '#list_type' => 'ul',
+    '#attributes' => array(),
+    '#items' => array(),
+  );
+
   /**
    * Input elements.
    */
diff --git a/modules/taxonomy/taxonomy.module b/modules/taxonomy/taxonomy.module
index 109397f3c16f481eb3b26515c07490a7bbe321d0..4e25c3bd8aac5da23d1f3a8445529f10745d3c3c 100644
--- a/modules/taxonomy/taxonomy.module
+++ b/modules/taxonomy/taxonomy.module
@@ -26,9 +26,6 @@ function taxonomy_theme() {
     'taxonomy_term_select' => array(
       'arguments' => array('element' => NULL),
     ),
-    'taxonomy_term_page' => array(
-      'arguments' => array('tids' => array(), 'result' => NULL),
-    ),
     'taxonomy_overview_vocabularies' => array(
       'arguments' => array('form' => array()),
     ),
@@ -73,7 +70,7 @@ function taxonomy_nodeapi_view($node) {
       }
     }
   }
-
+  
   $node->content['links']['terms'] = array(
     '#type' => 'node_links',
     '#value' => $links,
@@ -323,7 +320,7 @@ function taxonomy_term_save($term) {
     $status = drupal_write_record('taxonomy_term_data', $term);
     module_invoke_all('taxonomy_term_update', $term);
   }
-
+  
   $or = db_or()->condition('tid1', $term->tid)->condition('tid2', $term->tid);
   db_delete('taxonomy_term_relation')->condition($or)->execute();
 
@@ -1224,7 +1221,7 @@ function theme_taxonomy_term_select($element) {
  * @param $order
  *   The order clause for the query that retrieve the nodes.
  * @return
- *   A resource identifier pointing to the query results.
+ *   An array of node IDs.
  */
 function taxonomy_select_nodes($tids = array(), $operator = 'or', $depth = 0, $pager = TRUE, $order = 'n.sticky DESC, n.created DESC') {
   if (count($tids) > 0) {
@@ -1254,44 +1251,20 @@ function taxonomy_select_nodes($tids = array(), $operator = 'or', $depth = 0, $p
         $wheres .= ' AND tn' . $index . '.tid IN (' . db_placeholders($tids, 'int') . ')';
         $args = array_merge($args, $tids);
       }
-      $sql = 'SELECT DISTINCT(n.nid), n.sticky, n.title, n.created FROM {node} n ' . $joins . ' WHERE n.status = 1 ' . $wheres . ' ORDER BY ' . $order;
+      $sql = 'SELECT DISTINCT(n.nid) AS nid, n.sticky, n.created FROM {node} n ' . $joins . ' WHERE n.status = 1 ' . $wheres . ' ORDER BY ' . $order;
       $sql_count = 'SELECT COUNT(DISTINCT(n.nid)) FROM {node} n ' . $joins . ' WHERE n.status = 1 ' . $wheres;
     }
     $sql = db_rewrite_sql($sql);
     $sql_count = db_rewrite_sql($sql_count);
     if ($pager) {
-      $result = pager_query($sql, variable_get('default_nodes_main', 10), 0, $sql_count, $args);
+      $nids = pager_query($sql, variable_get('default_nodes_main', 10), 0, $sql_count, $args)->fetchCol();
     }
     else {
-      $result = db_query_range($sql, $args, 0, variable_get('feed_default_items', 10));
+      $nids = db_query_range($sql, $args, 0, variable_get('feed_default_items', 10))->fetchCol();
     }
   }
 
-  return $result;
-}
-
-/**
- * Accepts the result of a pager_query() call, such as that performed by
- * taxonomy_select_nodes(), and formats each node along with a pager.
- */
-function taxonomy_render_nodes($result) {
-  $output = '';
-  $nids = array();
-  foreach ($result as $record) {
-    $nids[] = $record->nid;
-  }
-  if (!empty($nids)) {
-    $nodes = node_load_multiple($nids);
-
-    foreach ($nodes as $node) {
-      $output .= node_view($node, 1);
-    }
-    $output .= theme('pager', NULL, variable_get('default_nodes_main', 10), 0);
-  }
-  else {
-    $output .= '<p>' . t('There are currently no posts in this category.') . '</p>';
-  }
-  return $output;
+  return $nids;
 }
 
 /**
diff --git a/modules/taxonomy/taxonomy.pages.inc b/modules/taxonomy/taxonomy.pages.inc
index 84ea1d3ca210af468d7cf5f9b208815d78644acc..16013aa1b4085468b7faf647b6c5ca83a1ec4a6d 100644
--- a/modules/taxonomy/taxonomy.pages.inc
+++ b/modules/taxonomy/taxonomy.pages.inc
@@ -42,11 +42,40 @@ function taxonomy_term_page($terms, $depth = 0, $op = 'page') {
           $breadcrumb[] = l(t('Home'), NULL);
           $breadcrumb = array_reverse($breadcrumb);
           drupal_set_breadcrumb($breadcrumb);
-
-          $output = theme('taxonomy_term_page', $tids, taxonomy_select_nodes($tids, $terms['operator'], $depth, TRUE));
           drupal_add_feed(url('taxonomy/term/' . $str_tids . '/' . $depth . '/feed'), 'RSS - ' . $title);
-          return $output;
-          break;
+          drupal_add_css(drupal_get_path('module', 'taxonomy') . '/taxonomy.css');
+
+          $build = array();
+          // Only display the description if we have a single term, to avoid clutter and confusion.
+          if (count($tids) == 1) {
+            $term = taxonomy_term_load($tids[0]);
+            if (!empty($term->description)) {
+              $build['term_description'] = array(
+                '#markup' => filter_xss_admin($term->description),
+                '#weight' => -1,
+                '#prefix' => '<div class="taxonomy-term-description">',
+                '#suffix' => '</div>',
+              );
+            }
+          }
+
+          if ($nids = taxonomy_select_nodes($tids, $terms['operator'], $depth, TRUE)) {
+            $nodes = node_load_multiple($nids);
+            $build += node_build_multiple($nodes);
+            $build['pager'] = array(
+              '#markup' => theme('pager', NULL, variable_get('default_nodes_main', 10)),
+              '#weight' => 5,
+            );
+          }
+          else {
+            $build['no_content'] = array(
+              '#prefix' => '<p>',
+              '#markup' => t('There are currently no posts in this category.'),
+              '#suffix' => '</p>',
+            );
+          }
+
+          return drupal_get_page($build);
 
         case 'feed':
           $channel['link'] = url('taxonomy/term/' . $str_tids . '/' . $depth, array('absolute' => TRUE));
@@ -58,13 +87,9 @@ function taxonomy_term_page($terms, $depth = 0, $op = 'page') {
             $channel['description'] = $term->description;
           }
 
-          $result = taxonomy_select_nodes($tids, $terms['operator'], $depth, FALSE);
-          $items = array();
-          while ($row = db_fetch_object($result)) {
-            $items[] = $row->nid;
-          }
+          $nids = taxonomy_select_nodes($tids, $terms['operator'], $depth, FALSE);
 
-          node_feed($items, $channel);
+          node_feed($nids, $channel);
           break;
 
         default:
@@ -77,38 +102,6 @@ function taxonomy_term_page($terms, $depth = 0, $op = 'page') {
   }
 }
 
-/**
- * Render a taxonomy term page HTML output.
- *
- * @param $tids
- *   An array of term ids.
- * @param $result
- *   A pager_query() result, such as that performed by taxonomy_select_nodes().
- *
- * @ingroup themeable
- */
-function theme_taxonomy_term_page($tids, $result) {
-  drupal_add_css(drupal_get_path('module', 'taxonomy') . '/taxonomy.css');
-  $output = '';
-
-  // Only display the description if we have a single term, to avoid clutter and confusion.
-  if (count($tids) == 1) {
-    $term = taxonomy_term_load($tids[0]);
-    $description = $term->description;
-
-    // Check that a description is set.
-    if (!empty($description)) {
-      $output .= '<div class="taxonomy-term-description">';
-      $output .= filter_xss_admin($description);
-      $output .= '</div>';
-    }
-  }
-
-  $output .= taxonomy_render_nodes($result);
-
-  return $output;
-}
-
 /**
  * Page to edit a vocabulary term.
  */
diff --git a/modules/upload/upload.test b/modules/upload/upload.test
index 79358225b9d47a366b68eb017d8a03e17cda0417..bd4bfbdc96a9e05afc2add77507f8dfc03a7c56a 100644
--- a/modules/upload/upload.test
+++ b/modules/upload/upload.test
@@ -53,7 +53,7 @@ class UploadTestCase extends DrupalWebTestCase {
 
     // Assure that the attachment link appears on teaser view and has correct count.
     $node = node_load($node->nid);
-    $teaser = node_view($node, TRUE);
+    $teaser = drupal_render(node_build($node, TRUE));
     $this->assertTrue(strpos($teaser, format_plural(2, '1 attachment', '@count attachments')), 'Attachments link found on node teaser.');
 
     // Fetch db record and use fid to rename and delete file.