Index: admin_menu.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/admin_menu/admin_menu.install,v
retrieving revision 1.4.2.6.2.1
diff -u -p -r1.4.2.6.2.1 admin_menu.install
--- admin_menu.install	8 Mar 2009 02:01:15 -0000	1.4.2.6.2.1
+++ admin_menu.install	31 Mar 2009 21:44:03 -0000
@@ -2,9 +2,28 @@
 // $Id: admin_menu.install,v 1.4.2.6.2.1 2009/03/08 02:01:15 sun Exp $
 
 /**
+ * Implementation of hook_schema().
+ */
+function admin_menu_schema() {
+  $schema['cache_admin_menu'] = drupal_get_schema_unprocessed('system', 'cache');
+  $schema['cache_admin_menu']['description'] = 'Cache table for Administration menu to store client-side caching hashes.';
+  return $schema;
+}
+
+/**
+ * Implementation of hook_install().
+ */
+function admin_menu_install() {
+  // Create cache table.
+  drupal_install_schema('admin_menu');
+}
+
+/**
  * Implementation of hook_uninstall().
  */
 function admin_menu_uninstall() {
+  // Remove cache table.
+  drupal_uninstall_schema('admin_menu');
   // Delete menu links.
   db_query("DELETE FROM {menu_links} WHERE module = 'admin_menu'");
   menu_cache_clear_all();
@@ -52,3 +71,14 @@ function admin_menu_update_6001() {
   return $ret;
 }
 
+/**
+ * Add {cache_admin_menu} table.
+ */
+function admin_menu_update_6300() {
+  $ret = array();
+  $schema = drupal_get_schema_unprocessed('system', 'cache');
+  $schema['description'] = 'Cache table for Administration menu to store client-side caching hashes.';
+  db_create_table($ret, 'cache_admin_menu', $schema);
+  return $ret;
+}
+
Index: admin_menu.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/admin_menu/admin_menu.js,v
retrieving revision 1.7.2.7.2.5
diff -u -p -r1.7.2.7.2.5 admin_menu.js
--- admin_menu.js	29 Mar 2009 04:24:11 -0000	1.7.2.7.2.5
+++ admin_menu.js	29 Mar 2009 04:24:56 -0000
@@ -11,15 +11,37 @@ Drupal.admin = Drupal.admin || { behavio
 Drupal.behaviors.adminMenu = function (context) {
   // Initialize settings.
   Drupal.settings.admin_menu = $.extend({
+    suppress: false,
     margin_top: false,
     position_fixed: false,
     tweak_modules: false,
     tweak_tabs: false,
-    destination: ''
+    destination: '',
+    basePath: Drupal.settings.basePath,
+    hash: 0
   }, Drupal.settings.admin_menu || {});
+  // Check whether administration menu should be suppressed.
+  if (Drupal.settings.admin_menu.suppress) {
+    return;
+  }
   var $adminMenu = $('#admin-menu:not(.admin-menu-processed)', context);
-  // Apply our behaviors.
-  Drupal.admin.attachBehaviors(context, $adminMenu);
+  // Client-side caching; if administration menu is not in the output, it is
+  // fetched from the server and cached in the browser.
+  if (!$adminMenu.length && Drupal.settings.admin_menu.hash) {
+    Drupal.admin.getCache(Drupal.settings.admin_menu.hash, function (response) {
+      if (typeof response == 'string' && response.length > 0) {
+        $('body', context).prepend(response);
+      }
+      var $adminMenu = $('#admin-menu:not(.admin-menu-processed)', context);
+      // Apply our behaviors.
+      Drupal.admin.attachBehaviors(context, $adminMenu);
+    });
+  }
+  // If the menu is in the output already, this means there is a new version.
+  else {
+    // Apply our behaviors.
+    Drupal.admin.attachBehaviors(context, $adminMenu);
+  }
 };
 
 /**
@@ -47,6 +69,25 @@ Drupal.behaviors.adminMenuMarginTop = fu
 };
 
 /**
+ * Retrieve content from client-side cache.
+ *
+ * @param hash
+ *   The md5 hash of the content to retrieve.
+ * @param onSuccess
+ *   A callback function invoked when the cache request was successful.
+ */
+Drupal.admin.getCache = function (hash, onSuccess) {
+  $.ajax({
+    cache: true,
+    type: 'GET',
+    dataType: 'text', // Prevent auto-evaluation of response.
+    global: false, // Do not trigger global AJAX events.
+    url: Drupal.settings.admin_menu.basePath + 'js/admin_menu/cache/' + hash,
+    success: onSuccess
+  });
+}
+
+/**
  * @defgroup admin_behaviors Administration behaviors.
  * @{
  */
Index: admin_menu.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/admin_menu/admin_menu.module,v
retrieving revision 1.43.2.17.2.3
diff -u -p -r1.43.2.17.2.3 admin_menu.module
--- admin_menu.module	29 Mar 2009 04:24:11 -0000	1.43.2.17.2.3
+++ admin_menu.module	1 Apr 2009 00:18:37 -0000
@@ -50,6 +50,12 @@ function admin_menu_theme() {
  * Implementation of hook_menu().
  */
 function admin_menu_menu() {
+  // AJAX callback.
+  $items['js/admin_menu/cache'] = array(
+    'page callback' => 'admin_menu_js_cache',
+    'access arguments' => array('access administration menu'),
+    'type' => MENU_CALLBACK,
+  );
   // Module settings.
   $items['admin/settings/admin_menu'] = array(
     'title' => 'Administration menu',
@@ -84,9 +90,15 @@ function admin_menu_menu() {
  * all pages via hook_init().
  */
 function admin_menu_init() {
-  if (!user_access('access administration menu')) {
+  if (!user_access('access administration menu') || admin_menu_suppress(FALSE)) {
     return;
   }
+  // Performance: Skip this entirely for AJAX requests.
+  if (strpos($_GET['q'], 'js/') === 0) {
+    return;
+  }
+  global $user, $language;
+
   $path = drupal_get_path('module', 'admin_menu');
   drupal_add_css($path . '/admin_menu.css', 'module', 'all', FALSE);
   // Performance: Defer execution.
@@ -95,6 +107,17 @@ function admin_menu_init() {
   // Destination query strings are applied via JS.
   $settings['destination'] = drupal_get_destination();
 
+  // Hash for client-side HTTP/AJAX caching.
+  $cid = 'admin_menu:' . $user->uid . ':' . $language->language;
+  if (!empty($_COOKIE['has_js']) && ($hash = admin_menu_cache_get($cid))) {
+    $settings['hash'] = $hash;
+    // The base path to use for cache requests depends on whether clean URLs
+    // are enabled, whether Drupal runs in a sub-directory, and on the language
+    // system configuration. url() already provides us the proper path, but we
+    // additionally need to ensure that it ends with a slash.
+    $settings['basePath'] = rtrim(url(''), '/') . '/';
+  }
+
   if ($setting = variable_get('admin_menu_margin_top', 1)) {
     $settings['margin_top'] = $setting;
   }
@@ -125,8 +148,10 @@ function admin_menu_init() {
  */
 function admin_menu_suppress($set = TRUE) {
   static $suppress = FALSE;
-  if (!empty($set)) {
+  // drupal_add_js() must only be invoked once.
+  if (!empty($set) && $suppress === FALSE) {
     $suppress = TRUE;
+    drupal_add_js(array('admin_menu' => array('suppress' => 1)), 'setting');
   }
   return $suppress;
 }
@@ -144,10 +169,24 @@ function admin_menu_footer($main = 0) {
   }
   global $user, $language;
 
+  // Determine whether we need to rebuild.
+  $rebuild = variable_get('admin_menu_rebuild_links', FALSE);
   $cid = 'admin_menu:' . $user->uid . ':' . $language->language;
 
+  // Do nothing at all here if the client supports client-side caching, no
+  // rebuild is needed, the user has a hash, and is NOT requesting the cache
+  // update path. Consult the hash cache last, since it requires a DB request.
+  // @todo Implement a sanity-check to prevent permanent double requests; i.e.
+  //   what if the client-side cache fails for any reason and performs a second
+  //   request on every page?
+  if (!empty($_COOKIE['has_js']) && !$rebuild && strpos($_GET['q'], 'js/admin_menu/cache') !== 0) {
+    if (admin_menu_cache_get($cid)) {
+      return;
+    }
+  }
+
   // Check for the flag indicating that we need to rebuild the menu.
-  if (variable_get('admin_menu_rebuild_links', FALSE)) {
+  if ($rebuild) {
     module_load_include('inc', 'admin_menu');
     _admin_menu_rebuild_links();
     variable_del('admin_menu_rebuild_links');
@@ -156,22 +195,103 @@ function admin_menu_footer($main = 0) {
   else {
     $cache = cache_get($cid, 'cache_menu');
     if ($cache && isset($cache->data)) {
-      return $cache->data;
+      $content = $cache->data;
     }
   }
 
   // Rebuild the output.
-  $content  = '<div id="admin-menu">';
-  $content .= admin_menu_tree_output(menu_tree_all_data('admin_menu'));
-  $content .= '</div>';
+  if (!isset($content)) {
+    $content  = '<div id="admin-menu">';
+    $content .= admin_menu_tree_output(menu_tree_all_data('admin_menu'));
+    $content .= '</div>';
+
+    // Cache the menu for this user.
+    cache_set($cid, $content, 'cache_menu');
+  }
 
-  // Cache the menu for this user.
-  cache_set($cid, $content, 'cache_menu');
+  // Store the new hash for this user.
+  if (!empty($_COOKIE['has_js'])) {
+    admin_menu_cache_set($cid, md5($content));
+  }
 
   return $content;
 }
 
 /**
+ * Retrieve a client-side cache hash from cache.
+ *
+ * The hash cache is consulted more than once per request; we therefore cache
+ * the results statically to avoid multiple database requests.
+ *
+ * This should only be used for client-side cache hashes. Use cache_menu for
+ * administration menu content.
+ *
+ * @param $cid
+ *   The cache ID of the data to retrieve.
+ */
+function admin_menu_cache_get($cid) {
+  static $cache = array();
+
+  if (!array_key_exists($cid, $cache)) {
+    $cache[$cid] = cache_get($cid, 'cache_admin_menu');
+    if ($cache[$cid] && isset($cache[$cid]->data)) {
+      $cache[$cid] = $cache[$cid]->data;
+    }
+  }
+
+  return $cache[$cid];
+}
+
+/**
+ * Store a client-side cache hash in persistent cache.
+ *
+ * This should only be used for client-side cache hashes. Use cache_menu for
+ * administration menu content.
+ *
+ * @param $cid
+ *   The cache ID of the data to retrieve.
+ */
+function admin_menu_cache_set($cid, $data) {
+  cache_set($cid, $data, 'cache_admin_menu');
+}
+
+/**
+ * Menu callback; Output administration menu for HTTP caching via AJAX request.
+ */
+function admin_menu_js_cache($hash = NULL) {
+  // Fetch the menu.
+  $content = admin_menu_footer();
+
+  // @todo According to http://www.mnot.net/blog/2006/05/11/browser_caching,
+  //   IE will only cache the content when it is compressed.
+  // Determine if the client accepts gzipped data.
+  if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && function_exists('gzencode')) {
+    if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) {
+      $encoding = 'gzip';
+    }
+    elseif (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'x-gzip') !== FALSE) {
+      $encoding = 'x-gzip';
+    }
+    if (!empty($encoding)) {
+      header('Vary: Accept-Encoding');
+      header('Content-Encoding: ' . $encoding);
+      $content = gzencode($content, 9, FORCE_GZIP);
+    }
+  }
+
+  $expires = time() + (3600 * 24 * 365);
+  header('Expires: ' . gmdate('D, d M Y H:i:s', $expires) . ' GMT');
+  header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
+  header('Cache-Control: max-age=' . $expires);
+  header('Content-Length: ' . strlen($content));
+
+  // Suppress Devel module.
+  $GLOBALS['devel_shutdown'] = FALSE;
+  echo $content;
+  exit;
+}
+
+/**
  * Return a rendered menu tree.
  *
  * @param $tree
