Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.1017
diff -u -p -r1.1017 common.inc
--- includes/common.inc	13 Oct 2009 21:16:42 -0000	1.1017
+++ includes/common.inc	14 Oct 2009 18:47:57 -0000
@@ -3150,7 +3150,7 @@ function drupal_clear_css_cache() {
  * @return
  *   The cleaned identifier.
  */
-function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_' => '-', '[' => '-', ']' => '')) {
+function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_' => '-', '/' => '-', '[' => '-', ']' => '')) {
   // By default, we filter using Drupal's coding standards.
   $identifier = strtr($identifier, $filter);
 
Index: includes/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.535
diff -u -p -r1.535 theme.inc
--- includes/theme.inc	14 Oct 2009 10:56:35 -0000	1.535
+++ includes/theme.inc	14 Oct 2009 18:47:57 -0000
@@ -2082,6 +2082,29 @@ function template_preprocess(&$variables
   $variables['attributes_array'] = array();
   $variables['title_attributes_array'] = array();
 
+  // Initialize a variable to hold any attached links. This is populated when
+  // the first item passed to the theme function is a page element that has
+  // links attached to it.
+  $variables['attached_links'] = array();
+  $element = reset($variables);
+  if (is_array($element) && !empty($element['#attached_links'])) {
+    $variables['attached_links'] = $element['#attached_links'];
+    // Add appropriate CSS classes that associate the element with the links
+    // that are attached to it.
+    $attached_link_classes = template_generate_attached_link_classes($element['#attached_links']);
+    if (!empty($attached_link_classes)) {
+      $variables['classes_array'][] = 'attached-links-enabled-region';
+      $variables['classes_array'] = array_merge($variables['classes_array'], $attached_link_classes);
+    }
+    // Wrap the attached links associated with this element in a theme function
+    // that will group them together for display. Templates which render
+    // different sets of attached links separately - for example, by using
+    // render($attached_links['block']) rather than render($attached_links) -
+    // will automatically avoid this wrapper function and therefore will have
+    // complete freedom to display the links anywhere within the page element.
+    $variables['attached_links']['#theme_wrappers'][] = 'attached_links_group';
+  }
+
   // Set default variables that depend on the database.
   $variables['is_admin']            = FALSE;
   $variables['is_front']            = FALSE;
@@ -2113,6 +2136,40 @@ function template_process(&$variables, $
 }
 
 /**
+ * Generate a list of relevant CSS classes from an array of attached links.
+ *
+ * The array of attached links is searched recursively for all items of type
+ * 'attached_links', and the 'href' parameter of each link is used to generate
+ * an appropriate CSS class.
+ *
+ * @param $attached_links
+ *   An associative array containing the links that will be searched. Usually,
+ *   this will be the '#attached_links' property of a page element.
+ * @return
+ *   An array of CSS classes generated from the attached links.
+ *
+ * @see system_pre_render_attached_links()
+ */
+function template_generate_attached_link_classes($attached_links) {
+  $classes = array();
+  // If the array itself contains attached links, generate classes from them.
+  if (isset($attached_links['#type']) && $attached_links['#type'] == 'attached_links') {
+    if (isset($attached_links['#links'])) {
+      foreach ($attached_links['#links'] as $link) {
+        $classes[] = drupal_html_class('attached-links-region-for-' . $link['href']);
+      }
+    }
+  }
+  // Otherwise, recursively search the element's children for links.
+  else {
+    foreach (element_children($attached_links) as $group) {
+      $classes = array_merge($classes, template_generate_attached_link_classes($attached_links[$group]));
+    }
+  }
+  return $classes;
+}
+
+/**
  * Preprocess variables for html.tpl.php
  *
  * @see system_elements()
Index: misc/attached_links.css
===================================================================
RCS file: misc/attached_links.css
diff -N misc/attached_links.css
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ misc/attached_links.css	14 Oct 2009 18:47:57 -0000
@@ -0,0 +1,62 @@
+/* $Id$ */
+
+/**
+ * Styling for attached links and the page regions they are associated with.
+ */
+.attached-links-link {
+  -moz-border-radius: 3px;
+  -webkit-border-radius: 3px;
+  background: #444 none no-repeat scroll 2px 2px;
+  height: 19px;
+  width: 19px;
+  float: left;
+  display: block;
+  margin-right: 2px;
+}
+
+.attached-links-link-configure {
+  background-image: url(configure.png);
+}
+
+.attached-links-link-delete {
+  background-image: url(delete.png);
+}
+
+.attached-links-link-edit {
+  background-image: url(edit.png);
+}
+
+.attached-links-enabled-region {
+  outline: none;
+  position: relative;
+}
+
+.active-attached-links-region {
+  outline: #000 dashed 2px;
+}
+
+.attached-links-group {
+  position: absolute;
+  right: 0;
+  top: 0;
+  padding: 0;
+  margin: 0;
+}
+
+ul.attached-links {
+  float: right;
+  padding: 0;
+  margin: 0;
+}
+
+ul.attached-links li {
+  padding: 0;
+  margin: 0;
+  list-style: none;
+  display: inline;
+  line-height: 100%;
+}
+
+ul.attached-links li a {
+  text-decoration: none;
+}
Index: misc/attached_links.js
===================================================================
RCS file: misc/attached_links.js
diff -N misc/attached_links.js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ misc/attached_links.js	14 Oct 2009 18:47:57 -0000
@@ -0,0 +1,31 @@
+// $Id$
+(function ($) {
+
+/**
+ * Highlights the region of the page that an attached link is associated with.
+ */
+Drupal.behaviors.attachedLinks = {
+  attach: function (context) {
+    var addHighlight = function () {
+      // If the attached link has a CSS class containing an encoded version of
+      // the URL that the link points to, find the region of the page whose CSS
+      // class contains the same encoded URL.
+      var matches = $(this).attr('class').match(/\battached-links-link-to-(\S+)/);
+      if (matches) {
+        var className = '.attached-links-region-for-' + matches[1];
+        $(className).addClass('active-attached-links-region');
+        $(this).addClass('active-attached-links-link');
+      }
+    };
+
+    var removeHighlight = function () {
+      $('.active-attached-links-region').removeClass('active-attached-links-region');
+      $('.active-attached-links-link').removeClass('active-attached-links-link');
+    };
+
+    // Trigger the behavior when hovering over the link.
+    $('.attached-links-link').hover(addHighlight, removeHighlight);
+  }
+};
+
+})(jQuery);
Index: modules/block/block.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block.api.php,v
retrieving revision 1.8
diff -u -p -r1.8 block.api.php
--- modules/block/block.api.php	31 Aug 2009 17:06:08 -0000	1.8
+++ modules/block/block.api.php	14 Oct 2009 18:47:58 -0000
@@ -151,6 +151,70 @@ function hook_block_view($delta = '') {
 }
 
 /**
+ * Perform alterations to the content of a block.
+ *
+ * This hook allows you to modify any data returned by hook_block_view().
+ *
+ * Note that instead of hook_block_view_alter(), which is called for all
+ * blocks, you can also use hook_block_view_MODULE_DELTA_alter() to alter a
+ * specific block.
+ *
+ * @param $data
+ *   An array of data containing the keys 'subject' and 'content', as returned
+ *   from the hook_block_view() implementation of the module that defined the
+ *   block. Note that $data['content'] may be either a string or a renderable
+ *   array, so you should check that the content is actually an array before
+ *   trying to modify part of the renderable structure.
+ * @param $module
+ *   The name of the module that defined the block.
+ * @param $delta
+ *   The identifier for the block within that module, as defined within
+ *   hook_block_info().
+ */
+function hook_block_view_alter(&$data, $module, $delta) {
+  // Remove the contextual links attached to all blocks that provide them.
+  if (is_array($data['content']) && isset($data['content']['#attached_links'])) {
+    unset($data['content']['#attached_links']);
+  }
+  // Add a theme wrapper function defined by the current module to all blocks
+  // provided by the "somemodule" module.
+  if (is_array($data['content']) && $module == 'somemodule') {
+    $data['content']['#theme_wrappers'][] = 'mymodule_special_block';
+  }
+}
+
+/**
+ * Perform alterations to a specific block.
+ *
+ * Modules can implement hook_block_view_MODULE_DELTA_alter() to modify a
+ * specific block, rather than implementing hook_block_view_alter() and either
+ * checking for specific blocks via an if statement or using a long switch
+ * statement to alter multiple blocks in the same function.
+ *
+ * Note that this hook fires before hook_block_view_alter(). Therefore, all
+ * implementations of hook_block_view_MODULE_DELTA_alter() will run before all
+ * implementations of hook_block_view_alter(), regardless of the module order.
+ *
+ * @param $data
+ *   An array of data containing the keys 'subject' and 'content', as returned
+ *   from the hook_block_view() implementation of the module that defined the
+ *   block. Note that $data['content'] may be either a string or a renderable
+ *   array, so you should check that the content is actually an array before
+ *   trying to modify part of the renderable structure.
+ *
+ * @see hook_block_view_alter().
+ */
+function hook_block_view_MODULE_DELTA_alter(&$data) {
+  // This code will only run for a specific block. For example, if MODULE_DELTA
+  // in the function definition above is set to "mymodule_somedelta", the code
+  // will only run on the "somedelta" block provided by the "mymodule" module.
+
+  // Change the title of the "somedelta" block provided by the "mymodule"
+  // module.
+  $data['subject'] = t('New title of the block');
+}
+
+/**
  * Act on blocks prior to rendering.
  *
  * This hook allows you to add, remove or modify blocks in the block list. The
Index: modules/block/block.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block.module,v
retrieving revision 1.389
diff -u -p -r1.389 block.module
--- modules/block/block.module	14 Oct 2009 02:13:14 -0000	1.389
+++ modules/block/block.module	14 Oct 2009 18:47:58 -0000
@@ -280,6 +280,26 @@ function _block_get_renderable_array($li
   foreach ($list as $key => $block) {
     $build[$key] = $block->content;
     unset($block->content);
+    // Attach links to the block. Skip this for empty blocks and also for the
+    // main system content block, which has nothing useful to configure.
+    if (!empty($build[$key]) && ($block->module != 'system' || $block->delta != 'main')) {
+      $build[$key]['#attached_links'] = array(
+        // First add the configuration links for the block itself.
+        'block' => array(
+          '#type' => 'attached_links',
+          '#links' => array(
+            'configure' => array(
+              'href' => "admin/structure/block/configure/{$block->module}/{$block->delta}",
+              'title' => t('Configure'),
+              'description' => !empty($block->subject) ? t('Configure the !block block', array('!block' => drupal_strtolower($block->subject))) : t('Configure this block'),
+            ),
+          ),
+        ),
+        // Now attach any links that were previously attached to the block
+        // content, so we can group them together with the links above.
+        'block content' => !empty($build[$key]['#attached_links']) ? $build[$key]['#attached_links'] : array(),
+      );
+    }
     $build[$key] += array(
       '#block' => $block,
       '#weight' => ++$weight,
@@ -752,6 +772,10 @@ function _block_render_blocks($region_bl
       }
       else {
         $array = module_invoke($block->module, 'block_view', $block->delta);
+        // Allow modules to modify the block before it is viewed, via either
+        // hook_block_view_MODULE_DELTA_alter() or hook_block_view_alter().
+        drupal_alter("block_view_{$block->module}_{$block->delta}", $array);
+        drupal_alter('block_view', $array, $block->module, $block->delta);
         if (isset($cid)) {
           cache_set($cid, $array, 'cache_block', CACHE_TEMPORARY);
         }
Index: modules/block/block.tpl.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block.tpl.php,v
retrieving revision 1.4
diff -u -p -r1.4 block.tpl.php
--- modules/block/block.tpl.php	11 Sep 2009 06:48:02 -0000	1.4
+++ modules/block/block.tpl.php	14 Oct 2009 18:47:58 -0000
@@ -11,6 +11,12 @@
  * - $block->module: Module that generated the block.
  * - $block->delta: An ID for the block, unique within each module.
  * - $block->region: The block region embedding the current block.
+ * - $attached_links: An array of links that are attached to the block on the
+ *   page. Use render($attached_links) to print them all, or print a subset
+ *   using render($attached_links['block']), which prints the configuration
+ *   links for the block itself, or render($attached_links['block content']),
+ *   which prints the links associated with the content that is displayed in
+ *   the block.
  * - $classes: String of classes that can be used to style contextually through
  *   CSS. It can be manipulated through the variable $classes_array from
  *   preprocess functions. The default values can be one or more of the following:
@@ -36,6 +42,11 @@
  */
 ?>
 <div id="block-<?php print $block->module . '-' . $block->delta; ?>" class="<?php print $classes; ?>"<?php print $attributes; ?>>
+ 
+<?php if ($attached_links): ?>
+  <?php print render($attached_links); ?>
+<?php endif; ?>
+ 
 <?php if ($block->subject): ?>
   <h2<?php print $title_attributes; ?>><?php print $block->subject ?></h2>
 <?php endif;?>
Index: modules/locale/locale.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.test,v
retrieving revision 1.44
diff -u -p -r1.44 locale.test
--- modules/locale/locale.test	13 Oct 2009 21:34:14 -0000	1.44
+++ modules/locale/locale.test	14 Oct 2009 18:47:59 -0000
@@ -1087,7 +1087,7 @@ class LanguageSwitchingFunctionalTest ex
     $this->assertText(t('Languages'), t('Language switcher block found.'));
 
     // Assert that only the current language is marked as active.
-    list($language_switcher) = $this->xpath('//div[@id="block-locale-language"]');
+    list($language_switcher) = $this->xpath('//div[@id="block-locale-language"]/div[@class="content"]');
     $links = array(
       'active' => array(),
       'inactive' => array(),
@@ -1096,7 +1096,7 @@ class LanguageSwitchingFunctionalTest ex
       'active' => array(),
       'inactive' => array(),
     );
-    foreach ($language_switcher->div->ul->li as $link) {
+    foreach ($language_switcher->ul->li as $link) {
       $classes = explode(" ", (string) $link['class']);
       list($language) = array_intersect($classes, array('en', 'fr'));
       if (in_array('active', $classes)) {
Index: modules/menu/menu.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.module,v
retrieving revision 1.209
diff -u -p -r1.209 menu.module
--- modules/menu/menu.module	13 Oct 2009 01:24:07 -0000	1.209
+++ modules/menu/menu.module	14 Oct 2009 18:47:59 -0000
@@ -431,10 +431,49 @@ function menu_block_view($delta = '') {
   $menus = menu_get_menus(FALSE);
   $data['subject'] = check_plain($menus[$delta]);
   $data['content'] = menu_tree($delta);
+  if (!empty($data['content'])) {
+    $data['content']['#attached_links'] = menu_attached_links($delta, $menus[$delta]);
+  }
   return $data;
 }
 
 /**
+ * Implement hook_block_view_alter().
+ */
+function menu_block_view_alter(&$data, $module, $delta) {
+  // Attach links to the system menus.
+  if ($module == 'system' && !empty($data['content'])) {
+    $system_menus = menu_list_system_menus();
+    if (isset($system_menus[$delta])) {
+      $data['content']['#attached_links'] = menu_attached_links($delta, $system_menus[$delta]);
+    }
+  }
+}
+
+/**
+ * Prepare a set of attached links for a menu.
+ *
+ * @param $menu_name
+ *   The machine-readable name of the menu.
+ * @param $title
+ *   The title that is displayed for the menu.
+ * @return
+ *   An array representing the attached links.
+ */
+function menu_attached_links($menu_name, $title) {
+  return array(
+    '#type' => 'attached_links',
+    '#links' => array(
+      'edit' => array(
+        'href' => 'admin/structure/menu/manage/' . $menu_name,
+        'title' => t('Edit'),
+        'description' => t('Edit the !menu menu', array('!menu' => drupal_strtolower($title))),
+      ),
+    ),
+  );
+}
+
+/**
  * Implement hook_node_insert().
  */
 function menu_node_insert($node) {
Index: modules/node/node.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.api.php,v
retrieving revision 1.39
diff -u -p -r1.39 node.api.php
--- modules/node/node.api.php	10 Oct 2009 21:39:03 -0000	1.39
+++ modules/node/node.api.php	14 Oct 2009 18:47:59 -0000
@@ -511,7 +511,7 @@ function hook_node_validate($node, $form
 }
 
 /**
- * The node content is being assembled before rendering.
+ * The node is being assembled before rendering.
  *
  * TODO D7 This needs work to clearly explain the different build modes.
  *
@@ -519,6 +519,12 @@ function hook_node_validate($node, $form
  * will be called after hook_view(). The structure of $node->content is a
  * renderable array as expected by drupal_render().
  *
+ * The module may also add elements to $node->build_data. This is used for
+ * additional contextual data (such as contextual links) that are not part of
+ * the node content but that may still be rendered along with the node. The
+ * structure of $node->build_data is a renderable array as expected by
+ * drupal_render().
+ *
  * When $build_mode is 'rss', modules can also add extra RSS elements and
  * namespaces to $node->rss_elements and $node->rss_namespaces respectively for
  * the RSS item generated for this node.
@@ -542,10 +548,11 @@ function hook_node_view($node, $build_mo
 }
 
 /**
- * The node content was built, the module may modify the structured content.
+ * The node was built; the module may modify the structured content.
  *
  * This hook is called after the content has been assembled in $node->content
- * and may be used for doing processing which requires that the complete node
+ * and any additional build data has been assembled in $node->build_data. It
+ * may be used for doing processing which requires that the complete node
  * content structure has been built.
  *
  * If the module wishes to act on the rendered HTML of the node rather than the
@@ -565,6 +572,9 @@ function hook_node_build_alter($node, $b
     $node->content['an_additional_field']['#weight'] = -10;
   }
 
+  // Do not show a delete link for this node.
+  unset($node->build_data['#attached_links']['#links']['delete']);
+
   // Add a #post_render callback to act on the rendered HTML of the node.
   $node->content['#post_render'][] = 'my_module_node_post_render';
 }
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1145
diff -u -p -r1.1145 node.module
--- modules/node/node.module	11 Oct 2009 03:07:18 -0000	1.1145
+++ modules/node/node.module	14 Oct 2009 18:48:00 -0000
@@ -1096,12 +1096,13 @@ function node_revision_delete($revision_
  *   An array as expected by drupal_render().
  */
 function node_build($node, $build_mode = 'full') {
-  // Populate $node->content with a render() array.
+  // Populate $node->content and $node->build_data with renderable arrays.
   node_build_content($node, $build_mode);
 
-  $build = $node->content;
-  // We don't need duplicate rendering info in node->content.
+  $build = $node->content + $node->build_data;
+  // We don't need duplicate rendering info.
   unset($node->content);
+  unset($node->build_data);
   
   $build += array(
     '#theme' => 'node',
@@ -1112,7 +1113,7 @@ function node_build($node, $build_mode =
 }
 
 /**
- * Builds a structured array representing the node's content.
+ * Builds a structured array representing the node and its content.
  *
  * The content built for the node (field values, comments, file attachments or
  * other node components) will vary depending on the $build_mode parameter.
@@ -1166,6 +1167,29 @@ function node_build_content($node, $buil
     '#attributes' => array('class' => array('links', 'inline')),
   );
 
+  // Attach contextual links to the node when it is being viewed outside of
+  // its primary context.
+  if (empty($node->build_data)) {
+    $node->build_data = array();
+  }
+  $node->build_data += array(
+    '#attached_links' => array(
+      '#type' => 'attached_links',
+      '#links' => array(
+        'edit' => array(
+          'href' => "node/{$node->nid}/edit",
+          'title' => t('Edit'),
+          'description' => t('Edit !type !node', array('!type' => drupal_strtolower(node_type_get_name($node)), '!node' => drupal_strtolower($node->title[FIELD_LANGUAGE_NONE][0]['value']))),
+        ),
+        'delete' => array(
+          'href' => "node/{$node->nid}/delete",
+          'title' => t('Delete'),
+          'description' => t('Delete !type !node', array('!type' => drupal_strtolower(node_type_get_name($node)), '!node' => drupal_strtolower($node->title[FIELD_LANGUAGE_NONE][0]['value']))),
+        ),
+      ),
+    ),
+  );
+
   // Allow modules to make their own additions to the node.
   module_invoke_all('node_view', $node, $build_mode);
 
Index: modules/node/node.tpl.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.tpl.php,v
retrieving revision 1.24
diff -u -p -r1.24 node.tpl.php
--- modules/node/node.tpl.php	11 Oct 2009 06:43:33 -0000	1.24
+++ modules/node/node.tpl.php	14 Oct 2009 18:48:00 -0000
@@ -18,6 +18,8 @@
  * - $node_url: Direct url of the current node.
  * - $terms: the themed list of taxonomy term links output from theme_links().
  * - $display_submitted: whether submission information should be displayed.
+ * - $attached_links: An array of links that are attached to the node on the
+ *   page. Use render($attached_links) to print them all.
  * - $classes: String of classes that can be used to style contextually through
  *   CSS. It can be manipulated through the variable $classes_array from
  *   preprocess functions. The default values can be one or more of the following:
@@ -74,6 +76,10 @@
 
   <?php print $user_picture; ?>
 
+  <?php if (!$page && $attached_links): ?>
+    <?php print render($attached_links); ?>
+  <?php endif; ?>
+
   <?php if (!$page): ?>
     <h2<?php print $title_attributes; ?>><a href="<?php print $node_url; ?>"><?php print $node_title; ?></a></h2>
   <?php endif; ?>
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.810
diff -u -p -r1.810 system.module
--- modules/system/system.module	13 Oct 2009 21:34:15 -0000	1.810
+++ modules/system/system.module	14 Oct 2009 18:48:01 -0000
@@ -208,6 +208,12 @@ function system_theme() {
       'arguments' => array('form' => NULL),
       'file' => 'system.admin.inc',
     ),
+    'attached_links' => array(
+      'arguments' => array('links' => NULL, 'attributes' => array(), 'heading' => array()),
+    ),
+    'attached_links_group' => array(
+      'arguments' => array('elements' => NULL),
+    ),
   ));
 }
 
@@ -248,6 +254,10 @@ function system_permission() {
       'title' => t('Block IP addresses'),
       'description' => t('Block IP addresses from accessing your site.'),
     ),
+    'view contextual links' => array(
+      'title' => t('View contextual links'),
+      'description' => t('Access a global "edit mode" which displays contextual links associated with items on the page.'),
+    ),
   );
 }
 
@@ -307,6 +317,16 @@ function system_element_info() {
     '#attributes' => array(),
     '#items' => array(),
   );
+  $types['attached_links'] = array(
+    '#theme' => 'attached_links',
+    '#pre_render' => array('system_pre_render_attached_links'),
+    '#attributes' => array('class' => array('attached-links')),
+    '#links' => array(),
+    '#attached' => array(
+      'js' => array('misc/attached_links.js'),
+      'css' => array('misc/attached_links.css'),
+    ),
+  );
 
   // Input elements.
   $types['submit'] = array(
@@ -511,6 +531,14 @@ function system_menu() {
     'type' => MENU_CALLBACK,
     'file' => 'system.admin.inc',
   );
+
+  $items['edit-mode/toggle'] = array(
+    'title callback' => 'edit_mode_toggle_title',
+    'page callback' => 'edit_mode_toggle',
+    'access arguments' => array('view contextual links'),
+    'type' => MENU_CALLBACK,
+  );
+
   $items['admin'] = array(
     'title' => 'Administer',
     'access arguments' => array('access administration pages'),
@@ -1394,6 +1422,13 @@ function blocked_ip_load($iid) {
 }
 
 /**
+ * Title callback for the page edit mode toggle.
+ */
+function edit_mode_toggle_title() {
+  return empty($_SESSION['edit_mode']) ? t('Enable edit mode') : t('Disable edit mode');
+}
+
+/**
  * Menu item access callback - only admin or enabled themes can be accessed.
  */
 function _system_themes_access($theme) {
@@ -2801,6 +2836,20 @@ function system_timezone($abbreviation =
 }
 
 /**
+ * Menu callback; Toggle the global edit mode and redirect.
+ */
+function edit_mode_toggle() {
+  if (!empty($_SESSION['edit_mode'])) {
+    unset($_SESSION['edit_mode']);
+  }
+  else {
+    $_SESSION['edit_mode'] = 1;
+  }
+  $referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '<front>';
+  drupal_goto($referer);
+}
+
+/**
  * Format the Powered by Drupal text.
  *
  * @ingroup themeable
@@ -3361,3 +3410,129 @@ function system_date_format_delete($dfid
     ->execute();
 }
 
+/**
+ * Checks access, adds classes, and performs other actions on attached links.
+ *
+ * This is used as a pre-render function for the #attached_links type.
+ */
+function system_pre_render_attached_links($element) {
+  if (empty($_SESSION['edit_mode']) || !user_access('view contextual links')) {
+    // Do not show attached links when the edit mode is disabled or when the
+    // user does not have permission to view them.
+    return array();
+  }
+
+  $transformed_links = array();
+  foreach ($element['#links'] as $type => $link_data) {
+    // Use the menu item to determine access to the link, if an access property
+    // was not already set.
+    if (!isset($link_data['#access'])) {
+      $item = menu_get_item($link_data['href']);
+      $link_data['#access'] = !empty($item['access']);
+    }
+
+    // Do not display this link if the current user does not have access to it,
+    // or if the user is already on the page that is being linked to.
+    if (!$link_data['#access'] || $link_data['href'] == $_GET['q']) {
+      continue;
+    }
+
+    // Get the link title and description from the menu item if none were set.
+    if (!isset($link_data['title']) || !isset($link_data['description'])) {
+      $item = menu_get_item($link_data['href']);
+      $link_data += array(
+        'title' => $item['title'],
+        'description' => $item['description'],
+      );
+    }
+
+    // Define classes for the link item.
+    $link_item_classes = array(
+      'attached-links-item',
+      drupal_html_class("attached-links-item-$type"),
+    );
+    $link_item_classes = implode(' ', $link_item_classes);
+
+    // Define classes for the link itself.
+    $existing_link_classes = !empty($link_data['attributes']['class']) ? $link_data['attributes']['class'] : array();
+    $link_classes = array_merge($existing_link_classes, array(
+      "attached-links-link",
+      "attached-links-link-$type",
+      // Associate the link with the region of the page it is attached to. See
+      // template_generate_attached_link_classes() for the matching class
+      // attached to the page region.
+      drupal_html_class('attached-links-link-to-' . $link_data['href']),
+    ));
+
+    // Transform and add to the link.
+    $transformed_links[$link_item_classes] = array(
+      'href' => $link_data['href'],
+      // Refer users back to the current page after they have completed any
+      // action (for example, a form submission) associated with the attached
+      // link.
+      'query' => drupal_get_destination(),
+      // Make sure that the link description will appear on hover.
+      'title' => $link_data['title'],
+      'attributes' => array(
+        'class' => $link_classes,
+        'title' => $link_data['description'],
+      ),
+    );
+  }
+
+  // Replace the links data with the recomputed items.
+  $element['#links'] = $transformed_links;
+  return $element;
+}
+
+/**
+ * Theme a set of attached links.
+ *
+ * @param $variables
+ *   An associative array containing the same properties that are passed in to
+ *   theme_links().
+ *
+ * @return
+ *   A themed HTML string representing the set of attached links.
+ *
+ * @ingroup themeable
+ * @see theme_links()
+ */
+function theme_attached_links($variables) {
+  foreach ($variables['links'] as &$link) {
+    // Wrap the link title in a class that will allow it to be displayed as an
+    // icon instead. We therefore need to mark this link as HTML and make sure
+    // to sanitize it ourselves if necessary, since theme_links() will not.
+    $sanitized_title = !empty($link['html']) ? $link['title'] : check_plain($link['title']);
+    $link['title'] = '<span class="element-invisible">' . $sanitized_title . '</span>';
+    $link['html'] = TRUE;
+  }
+  return theme('links', $variables);
+}
+
+/**
+ * Theme a group of attached links.
+ *
+ * A group of attached links can contain multiple "sets" of attached links,
+ * where each set is themed via theme_attached_links(). This function is
+ * intended to be a simple wrapper that allows all sets associated with an
+ * element to be grouped together, while still allowing templates to pull out
+ * sets individually and render them in different places (for example, the
+ * set of attached links associated with configuring a block may need to be
+ * displayed in a different place as the set of attached links associated with
+ * the content of the block).
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - elements: An associative array containing the properties of the element.
+ *     Properties used: #children
+ *
+ * @return
+ *   A themed HTML string representing the group of attached links.
+ *
+ * @ingroup themeable
+ * @see theme_attached_links()
+ */
+function theme_attached_links_group($variables) {
+  return '<div class="attached-links-group">' . $variables['elements']['#children'] . '</div>';
+}
Index: modules/toolbar/toolbar.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/toolbar/toolbar.install,v
retrieving revision 1.6
diff -u -p -r1.6 toolbar.install
--- modules/toolbar/toolbar.install	13 Oct 2009 13:54:55 -0000	1.6
+++ modules/toolbar/toolbar.install	14 Oct 2009 18:48:02 -0000
@@ -27,7 +27,11 @@ function toolbar_install() {
     'node/add' => 'Add content',
     'admin/content' => 'Find content',
     'admin/dashboard' => 'Dashboard',
+    // No title, so the title callback associated with the menu item will be
+    // used instead.
+    'edit-mode/toggle' => '',
   );
+
   $weight = -20;
   foreach ($items as $path => $title) {
     $link = array(
Index: themes/garland/block.tpl.php
===================================================================
RCS file: /cvs/drupal/drupal/themes/garland/block.tpl.php,v
retrieving revision 1.9
diff -u -p -r1.9 block.tpl.php
--- themes/garland/block.tpl.php	11 Sep 2009 06:48:03 -0000	1.9
+++ themes/garland/block.tpl.php	14 Oct 2009 18:48:02 -0000
@@ -3,6 +3,10 @@
 ?>
 <div id="block-<?php print $block->module . '-' . $block->delta; ?>" class="<?php print $classes; ?> clearfix"<?php print $attributes; ?>>
 
+<?php if ($attached_links): ?>
+  <?php print render($attached_links); ?>
+<?php endif; ?>
+
 <?php if (!empty($block->subject)): ?>
   <h2 class="title"<?php print $title_attributes; ?>><?php print $block->subject ?></h2>
 <?php endif;?>
Index: themes/garland/node.tpl.php
===================================================================
RCS file: /cvs/drupal/drupal/themes/garland/node.tpl.php,v
retrieving revision 1.17
diff -u -p -r1.17 node.tpl.php
--- themes/garland/node.tpl.php	11 Oct 2009 03:07:21 -0000	1.17
+++ themes/garland/node.tpl.php	14 Oct 2009 18:48:02 -0000
@@ -3,6 +3,10 @@
 ?>
 <div id="node-<?php print $node->nid; ?>" class="<?php print $classes; ?>"<?php print $attributes; ?>>
 
+  <?php if (!$page && $attached_links): ?>
+    <?php print render($attached_links); ?>
+  <?php endif; ?>
+
   <?php print $user_picture; ?>
 
   <?php if (!$page): ?>
Index: themes/garland/style.css
===================================================================
RCS file: /cvs/drupal/drupal/themes/garland/style.css,v
retrieving revision 1.65
diff -u -p -r1.65 style.css
--- themes/garland/style.css	5 Oct 2009 02:43:01 -0000	1.65
+++ themes/garland/style.css	14 Oct 2009 18:48:02 -0000
@@ -650,8 +650,8 @@ ul.secondary li.active a {
  */
 .node {
   border-bottom: 1px solid #e9eff3;
-  margin: 0 -26px 1.5em;
-  padding: 1.5em 26px;
+  margin: 0 -16px 1.5em;
+  padding: 1.5em 16px;
 }
 
 ul.links li, ul.inline li {
@@ -809,6 +809,32 @@ tr.even td.menu-disabled {
 }
 
 /**
+ * Styling for attached links.
+ */
+.attached-links-enabled-region {
+  outline: none;
+}
+
+.active-attached-links-region {
+  outline: dashed #027AC6 2px;
+}
+
+ul.attached-links li a {
+  text-decoration: none;
+  background-color: #027AC6;
+}
+
+ul.attached-links li a:hover {
+  background-color: #0062A0;
+}
+
+/* Override the styling applied to normal block lists. */
+.block ul.attached-links {
+  margin: 0;
+  padding: 0;
+}
+
+/**
  * Collapsible fieldsets
  */
 fieldset {
