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	29 Mar 2009 04:25:28 -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',
@@ -87,6 +93,8 @@ function admin_menu_init() {
   if (!user_access('access administration menu')) {
     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 +103,16 @@ function admin_menu_init() {
   // Destination query strings are applied via JS.
   $settings['destination'] = drupal_get_destination();
 
+  // Hash for client-side HTTP/AJAX caching.
+  $hash_key = 'admin_menu_hash_' . $language->language;
+  if (!empty($_COOKIE['has_js']) && !empty($GLOBALS['user']->{$hash_key})) {
+    $settings['hash'] = $GLOBALS['user']->{$hash_key};
+    // The base path to use for cache requests depends on whether clean URLs
+    // are enabled and on the language system configuration. url() provides us
+    // the proper path.
+    $settings['basePath'] = url('') . '/';
+  }
+
   if ($setting = variable_get('admin_menu_margin_top', 1)) {
     $settings['margin_top'] = $setting;
   }
@@ -125,8 +143,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 +164,24 @@ function admin_menu_footer($main = 0) {
   }
   global $user, $language;
 
+  // Determine whether we need to rebuild.
+  $rebuild = variable_get('admin_menu_rebuild_links', FALSE);
+  $hash_key = 'admin_menu_hash_' . $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.
+  // @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 && !empty($user->{$hash_key}) && strpos($_GET['q'], 'js/admin_menu/cache') !== 0) {
+    return;
+  }
+
   $cid = 'admin_menu:' . $user->uid . ':' . $language->language;
 
   // 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 +190,64 @@ 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.
+  // @todo Use an own storage for our hashs, so we can cache sub-menus as well.
+  if (!empty($_COOKIE['has_js'])) {
+    user_save($user, array($hash_key => md5($content)));
+  }
 
   return $content;
 }
 
 /**
+ * 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();
+
+  // 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
