For the same user on one Drupal site, the administration menu keeps the same on all pages. In theory (and after a quick test also in practice), the complete menu could be cached on the client-side.

In a first generation, the sessionvars JavaScript by Thomas Frank seems to be suitable. If that happens to works out, a second generation could use browser-specific implementations of sessionStorage (also exists in IE8).

sessionvars (ab)uses the window.name property, which allows to store approx. 2 MB of data - unsecured and across domains. More on this later on.

The battle plan as of now:

  • Since client-side caching requires JavaScript and we cannot determine whether a client has support for JavaScript, the client needs to tell us whether it supports JavaScript by invoking a JavaScript callback URL (e.g. http://example.com/admin-menu/has-js). When this callback is invoked, we store a flag that indicates the current user can cache the menu.
  • Due to the previous point, the first output of administration menu is always uncached (like now). However, subsequent page requests will not build and generate the menu when the user has been flagged to support JavaScript. Instead, two variables are generated for Drupal.settings:
    • a hash, probably a md5 of $base_url, $language, and $uid.
    • a salt, used to encrypt/decrypt the cached copy on the client-side and do not expose its contents to scripts on other domains.
  • Whenever #admin-menu is output in the HTML markup of a page, the client-side cache gets updated (forced).
  • The client-side cache variable, sessionvars.adminMenu, is an object with the aforementioned hash as property, having the administration menu as HTML string as value.
  • If the script is able to find the corresponding property for the current user (e.g. sessionvars.adminMenu.44b84194b9f38dfdbaa37219dccae4b5) AND it is able to decrypt its contents using the salt provided in Drupal.settings, the resulting HTML string is appended to the body (i.e. $('body').append('<div id="admin-menu">' + value + '</div>');.
  • If it cannot find or decrypt the menu, an AJAX request is performed to fetch a new copy of the menu that is immediately output, encrypted and cached.
  • Similarly, if Drupal's menu is rebuilt, the salt needs to change, which triggers a forced update of the client-side cached menu.
  • Since administration menu does not really contain private/sensible information, encryption happens primarily to hide its contents from prying eyes, but also to ensure that no malicious code was injected by 3rd parties (using the decryption salt provided in Drupal.settings). To keep it fast, a simple bit-shifting crypt may be sufficient; for example: http://www.braner-net.de/computer/software/jscrypt.htm or http://www.users.fh-salzburg.ac.at/~fhs14817/mailcrypt.htm

As mentioned before, a quick and dirty test unveiled that the whole approach of client-side caching would work. However, to proceed here, we need to ensure that this is safe (in terms of clients without JS-support) and secure (in terms of CSRF-attacks).

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

sun’s picture

smk-ka pointed out that we could simply use a cookie that holds the CRC of the rendered menu string. If the client-side cached menu does not has the same CRC, it needs to be rebuilt. Using a cookie also allows us to determine it on the server-side already.

sun’s picture

Version: 6.x-1.x-dev » 6.x-3.x-dev
markus_petrux’s picture

Thomas Frank's looks an interesting approach, but it looks like it could be tricky to avoid security related threads, or trying to keep data when user opens a new window.

http://www.thomasfrank.se/sessvarsTestPage1.html

That demo works as long as you stay no the same window, but the data is lost as soon as you open a new window, at least in FF3.

sessionStorage looks nicer, not so hack-ish.

I would say if admin_menu had the server side implementation ready for this kind of client side libraries, then a 3rd option could be using browser plugins.

sun’s picture

Note that I already talked with members of the security team about this issue. They did not see anything that would have to be secured. This means we will not even encrypt the menu on the client-side.

However, we will still need a CRC or "version counter" to trigger an update of the client-side cached menu when there is new data.

markus_petrux’s picture

FileSize
1.24 KB

Hi sun,

I got an idea today that I would like to show you. :-D

Well, the idea is that we could simply use the browser cache for the admin_menu. Tada!

I have created a tiny module that disables the admin_menu in hook_init, but only if the user has javascript enabled (it doesn't work otherwise), also except when a particular URL is requested. When it disables the admin_menu, it also sends a small javascript file that implements a Drupal behavior that loads the admin_menu using an AJAX request. The kit here is that request is performed against a URL that contains a hash in it, and that it is sent with a high cache storage in the headers, so it is cached by the browser, and it will be reused without further requests as far as the hash doesn't change. The hash is simply a MD5 of the admin_menu output.

Attached a tar with the tiny module that I called admin_menu_cache, but it could be included in admin_menu itself if you like the idea.

sun’s picture

VERY interesting. I did not think about this option before. I don't know whether it is better or not, but it obviously would be an alternative approach.

sun’s picture

Status: Active » Needs work

I want to see the client-side caching directly in admin_menu. Your approach looks very, very appealing. Though I still wonder whether sessionStorage would provide us some yet-unknown additional benefits and/or possibilities. At the very least, I think both approaches could even co-exist, depending on the browser being used (and of course, if there is a difference in possible functionality at all).

markus_petrux’s picture

Sweet! Thanks for looking at it. I'm so excited with this approach that I'm already using it for the sites I'm working on. :)

I have changed a few things, and might still be fixing issues so that I committed the admin_menu_cache thingy to my sandbox space.

Next, I would would be glad to write this as a patch to the admin_menu module, if you can provide some directions on how to approach this. Or maybe the code I committed can give you enough food to do it yourself, so that you better know how you could add this feature to the module. I believe I documented all the bits and it's just a tiny piece of code. Please, let me know if I can help any further. :)

sun’s picture

The most important thing when (or before) working on a patch is to flesh out the proper logic.

- For clients with JS support only the very first request should build and output the menu.

- For clients without JS support the behavior does not change at all.

- The client must cache per uid, language, site.

- Whenever the menu is rebuilt (not the JS), the client needs to fetch the newly built menu.

- On all pages using the client-side cache, admin_menu must not generate, build, or output the menu at all, which means zero impact on performance. Note, however, that we will still generate Drupal.settings, and will also generate any dynamic data (bells & whistles) for the menu, e.g. the menu item value for the user counter and similar stuff.

- If retrieval of the cached menu fails for any reason, there either must be an automatic and/or manual fallback logic. (In my original test, the default output of admin_menu for clients with JS support was a link titled "Click here if the administration menu does not show up.", which was replaced by the cached menu. Clicking on this link would have removed the has_js cookie, because obviously something went wrong or the browser failed somewhere [think mobile browsers aso.])

The more I think about this approach, the more I like it - because it's bad-ass simple and is based on HTTP standards.

markus_petrux’s picture

One thing that would make things easier is that dynamic elements in the menu (users online and username/link to profile) could be stored in separate cache_set() entries, so that the basic structure of the menu remains the same for more situations (different users). Then, current method to send the menu could just replace placeholders with current values.

If this was done over the rendered menu, the same process could be used for the client-cache mechanism. The server could send the admin menu with placeholders that could be replaced on the client by getting the values through a separate ajax request. This one would not be cached on the client, or maybe just using a shorter expire time.

There would be 2 methods:

a) current method would be used by non-javascript enabled user agents.
b) new method where a special javascript would be sent instead of the menu. This client code would perform 2 requests. One to obtain the HTML of the menu with placeholders for dynamic data, and another to obtain the values. The first request would be sent with a high expiration time so that it would be cached by the browser, and would only reach the server when the URL was new, or at expiration time. The second request would not be cached, or just with a short enough expiration time.

But all these stuff would be easier if admin_menu was caching the menu and the dynamic data in separate cache_set() entries.

sun’s picture

Let's keep this on topic. 3.x already caches the entire menu output. The output is only rebuilt if the menu is rebuilt. The implied issues resulting of this caching are already known, and yes, also the non-client-side-cached version will use JavaScript to update the data for specific menu items (one of them being the user counter) - but it will use Drupal.settings, because we do not really want to hit Drupal once again for this data (which would have the opposite result in terms of performance in the end). Admittedly, this dynamic update issue is not yet mentioned in #402058: Requirements for Administration menu in Drupal core though... ;)

So, a very rough description of what we want to achieve in this issue is: The client-side caching performs the same caching as the server-side caching. Both caches use the same cache key and also their caching behavior is identical. Only the cache storage differs.

The additional difference and challenge is that the menu is not visible nor functional when the client-side cache fails.

markus_petrux’s picture

I have updated the code in my sandbox. It now performs dynamic replacements of username, and online counters. I'm computing a hash of the admin menu HTML that is added to the URL that will perform the ajax request because this way, the same exact HTML can be shared between several users. If there's a proxy cache in the middle, the request will just hit the server for the first user requesting that version of the menu. That's a plus I wanted to achive.

If you look at admin_menu_cache_translated_menu_link_alter(), you'll see that I'm generating placeholders ranther that then data itself when the menu HTML is built. These placeholders are replaced later a) when sending the menu with the page, or b) by the client after performing the ajax request (that could come from a proxy). Replacemente are sent to the client via Drupal.settings.

Sorry, I believe I can explain better showing code.

sun’s picture

Please, can we please stay on topic? I'm really amazed that you're working on this, but if we do not focus on atomic issues, this won't result in anything that works. Dynamic update/replacement of data in the cached menu output is a completely separate issue, for which I would already have a lot to say, but not here. I really do not want to see any further word about that in this issue. If you really want to work in parallel on both issues, then we can create a new issue for it and discuss "over there".

Using a hash of the output sounded interesting in the first place. However, it would require that the menu output for multiple users on the same (multi-)site and in the same language is identical. That is the case for users sharing the same user roles / permissions. But admin_menu also contains user-specific items, such as the user's name - which I am not too sure we want to pass on and replace dynamically via Drupal.settings. Additionally, this would not really allow that any module or admin_menu itself could output user-specific tools and tweaks in the menu; for example, users might configure in their account settings that they want to see a Finder-alike search bar. Of course, your logic would still work. But I fear that we would extract too much user-specific data into dynamic replacements in the long run.

That said, I'm not completely sure about what I've said in the last paragraph. And because I'm not sure, I'd prefer us to work in smaller steps, so we can get a better idea of what is possible or makes sense and what not (similar to an agile development approach). Can we agree on that? For example, I've great success using this strategy in Wysiwyg API thus far.

For now we should focus on implementing the client-side cache exactly as the server-side cache, ensure that no user gets the wrong menu, the menu is correctly updated if there is a new version, the fallback works, and the ordinary menu is properly displayed for users without JavaScript. That's already a lot of functionality, and it's the core functionality for client-side caching, so we have to make sure that it is stable and fail-proof.

Regarding #9: Did you already take everything of that into account? Or do you have any amendments? If not, I'd be highly interested in a patch against 6.x-3.x or HEAD - which should not be hard to do.

markus_petrux’s picture

The client side cache cannot be implemented in the same exact way as in the server. I'm sending HTML, and that's what gets cached by the client (or proxies in the middle). On the other hand, the server caches structured data. I would say it's not optimal to cache HTML on the server the same way it's not optimal to cache structured data on the client side, because it will slow the page.

Computing a hash of the HTML built with placeholders ensures only the exact version of the menu will be sent to the user. If someone else changes the menu, the hash will be different so the client will request a URL that will bring the new updated HTML of the menu. If all users have different version, they will get different menus, but if some users have the same menu (except dynamic data) they will get the same menu, that could be server by an accelerator proxy such as squid, for example.

Aside from the client cache implementation details, I think the module I have in the sandbox covers all your requirements. Please, install and try for yourself if you haven't already. There may be bugs, there may be room for improvements (for example, adding a hook to allow any other module generate placeholders and replacements), but the code works, as far as I can tell. It is also compatible with admin_menu_suppress(). If you look at the code, I believe you'll easily see that it's close to admin_menu, so it's easy to figure out the patch that will bring this features to admin_menu itself.

Sorry if I talk about dynamic replacements, but since we use squid, it's important for us a model where proxy cache can deal with requests to the admin menu HTML. So I tried to cover that in my attempt to resolve this.

sun’s picture

Just to make sure we're speaking of the same version: All 3.x versions of admin_menu no longer cache structured data. They cache the final output of the menu; a simple HTML string. Which means that if there is a cached menu, it is output directly without any further processing. This was done as a prerequisite for this issue. See #349505: Performance: Cache menu tree for details (though the title of that issue could lead to wrong assumptions).

At least that's why my primary goal is get both caches working identically. Are we on the same line, do I miss something, or do we just talk at cross-purposes? ;)

markus_petrux’s picture

Oops, you're right. For some reason I kept the idea that it was caching structured data. I'm running latest 6.x-3.x-dev snapshot of admin_menu.

Sorry if that was confussing. My latest comments are still valid, though. I believe the code I have in the sandbox covers all your requirements. Please, install admin_menu_cache and try it. I forgot to mention the HTML is send gzipped if the browser supports, so a 60k menu consumes 7k bandwith, and if you have squid, the server won't see more than one request for your session, as far as the menu doesn't change of course. It was so exciting... :-D

sun’s picture

Status: Needs work » Needs review
FileSize
7.59 KB

Ok. Migrating this into admin_menu is not so trivial (just like I predicted).

I'll recap here now, primarily to get it sorted out for myself.

Client-side caching process:

- User logs in, gets a page with admin menu already contained. (Dunno when has_js cookie is really defined first)
- On the very same page, we already compute the hash for the menu. [STOP]
- User visits next page. Due to has_js cookie, admin menu is NOT output; it's not even loaded from server-side cache. Just the hash in Drupal.settings. [STOP]
- Client takes hash, prepends the cache URL, gets the menu. Due to our headers, the browser caches this request's content.
- User visits next page. Client gets hash; cache headers prevent from fetching the same URL again.
- User visits next page, but menu was rebuilt. Different hash in Drupal.settings makes the client fetch the new menu.

STOP == Where do we store the hash? $_SESSION? $user? $conf? $user for now.

We also want to prepare for http://drupal.org/project/js

Additional benefit: Regular Drupal behaviors are not applied to admin menu. We had issues with External Links and Google Analytics modules in the past, because both modules parse each and every link on a page (and admin menu can contain *many* links on large-scale sites).

Remaining todos:

- Menu stays the same when changing language/locale. We need to store hashes per language (and site?) in $user, i.e. using an array, keyed by xyz.

- Firebug tells me that each AJAX request still takes 128ms. Is this due to the bad performance of my local Apache server?

- admin menu behaviors are not applied on all pages. Seems like we need to disable async request for $.ajax.

sun’s picture

Note that I won't be able to work on this for the next few days due to other obligations. However, I'd really, really, really like to see this getting RTBC. This is incredible!

sun’s picture

Regarding the client-side cache key:

The solution is to use the very same cache key as the server-side.

$uid:$language->language:$hash

- We don't need $site, because the browser will query a different host anyway.
- (I think) we need $uid, because we have to support stuff like Devel's switch user.

That way, the first issue should be resolved.

markus_petrux’s picture

Status: Needs review » Needs work

This is incredible!

Yep! :-D

I haven't tested it, but it starts to look great!

Minor thing in your patch:

-  header('Cache-Control: max-age=' . 86400);
+  header('Cache-Control: max-age=86400');

Another thing. In misc/ahah.js we can see:

  // Manually insert HTML into the jQuery object, using $() directly crashes
  // Safari with long string lengths. http://dev.jquery.com/ticket/1152
  var new_content = $('<div></div>').html(response.data);

So, maybe I would do this:

-  $('body', context).append(response);
+  var new_content = $('<div></div>').html(response);
+  $('body').append(new_content);

Note that I do not use context for the body selector, as there is just one body in the document.

Also, how about storing the hash in the session? This feature affects logged in users, se the session will be updated anyway. And that way we don't add more clutter to the users table. Maybe this can save one or two milliseconds.

sun’s picture

Thanks for that ahah.js pointer - I wasn't aware of that.

Regarding storage, I already started brainstorming about this whole new thing. Long story short: I believe that we will be able to also cache on-demand loaded sub-menus by making the very same approach more generic. There was a feature in admin menu 5.x that listed existing views and panels below their respective "List" items in the menu, which will be resurrected in 3.x in a more generic way (see #293768: Allow to enable/disable menu additions about details). Also, the aforementioned search bar might use it. In all cases, it boils down to the logic: If the system data is still the same (e.g. no view was created/renamed/deleted), then the additional dynamic data is the same - here even for all users. Whether all of this will happen, is certainly a different question.

However, because of those preliminary thoughts, I'm rather leaning towards an own db table in the meantime, because sessions can expire very often (but the menu would still be the same).

markus_petrux’s picture

hmm... looks interesting. So maybe the ajax loader needs something more that just a hash to request a single "object", maybe an array of hashes, but then we need to know what to do with the responses. There maybe just one main stream, the menu itself, and then... ¿? ...for each item we need a hash and information on what to do with it. The same happens with data sent through Drupal.settings, it could be the online counters or maybe something else.

One thing that seems to be common is that all pieces will need to update the main "object", the menu itself. So this needs some kind of indicator that tells where each dynamic item should be placed. Since the main object is HTML that can be accessed via DOM, maybe these indicators need to be CSS classes and/or IDs. But we also need to perform dynamic replacements on the server, and here we don't have jQuery. hmm... when javascript is not enabled, how could we dynamically modify rendered HTML on the server? invoking hooks that can use preg_replace? Also using somekind of callbacks on the client that would be called by the gereric ajax loader?

Now, I see what you said before. Dynamic replacements really turns to be a bit more complex...

sun’s picture

After talking to smk-ka about the page request/generation times, it seems we can't decrease them any further. On regular requests, they take between 130-300ms. By implementing JS/AJAX callback handler, I was able to get them down to 90-120ms. Still too much.

The reason for those long request times is that Drupal is still invoked. jQuery's $.ajax() does not seem to hit the browser cache. We tinkered about implementing the ImageCache approach, i.e. dynamically creating and storing the cached output as a file. That way, Apache would directly return "Not Modified" and request times would decrease to ~5ms. The only "caveat" would be that those cache files /could/ be accessed by other users if they find a way to determine a user's hash. But then again, the security team said this would not matter...

sun’s picture

The current implementation breaks admin_menu_suppress(), which means other modules can no longer suppress admin_menu on certain pages. It seems admin_menu_suppress() needs to add a JS setting instead now.

markus_petrux’s picture

The client-side code that performs the ajax request could be placed on a separate .js file that would be sent with the page only when the ajax request needs to be performed. So the presence of this separate .js would be the trigger. This is how I solved it on my mini-module.

sun’s picture

Fixed the suppression issue.

Attaching current state of what is working so far for me.

Wim Leers’s picture

Very interesting stuff going on here. I might be able to take advantage of the techniques invented/applied here for Hierarchical Select's client-side caching :)
(It already has client-side caching, but it requires HTML 5 client-side databases. Which is only supported in Webkit atm …)

markus_petrux’s picture

@sun: Bug in #26: If admin_menu_suppress() is invoked more than once, then it will set Drupal.settings repeated times, and that will create an array in place of the .suppress value.

@Wim: the trick is sending the appropriate headers to the ajax request so the response can be cached client-side, and that's based on well stablished HTTP standards. :)

sun’s picture

Priority: Normal » Critical
FileSize
11.06 KB

@markus_petrux: Good point, fixed in attached patch.

Anyway, that's a bit too nitpicky for the current situation.

Where do we go from here? The current implementation _does not work_ as intended. As stated above, jQuery's ajax() does not account for the browser's cache and leads to a full, second Drupal bootstrap as it stands. Possible reasons/options:

a) jQuery's ajax() is buggy. Try to fix it, so it incorporates the local browser cache. When it is fixed, require jquery_update + latest version of jQuery for admin_menu 3.x.

b) Our returned HTTP headers are buggy, so the behavior of ajax() is right.

c) Our options for ajax() are buggy, so it properly requests a new version.

d) Current idea won't work and Drupal will always bootstrap. Implement an approach like ImageCache using file-based caching, so Apache handles the cache requests instead of Drupal.

e) Revert to the original idea of using Thomas Frank's sessionvars and/or sessionStorage.

I don't care whether the current implementation might work with a squid or any other HTTP proxy server in the middle. For 99.99% of all users, it does not work.

On b) As mentioned before, I already investigated this a bit, so I would say that our returned headers should be right.

On d) Caching the menu will probably be ok from a security perspective. But when it comes to caching sub-menu links (i.e. lists of existing system objects), I'm not sure whether that would expose too much.

So, where do we go from here?

markus_petrux’s picture

f) Maybe you're not analyzing correctly if the menu is actually cached by the browser? Have you looked at your server logs to see if you have a second request after the ajax request has been cached by the browser?

g) Maybe there's something wrong with the stuff related to gzip compression that works on my env. but not in yours? Could you try commenting that snippet and see if now caches correctly?

I have only browser your patch so I can't tell for sure, but my mini module works. :P ...I know it works because I checked my server logs to see how many entries there were about the ajax request.

Could you please try my mini module with admin_menu 6.x-3-x-dev (without your patch here of course) and see if that works for you?

sun’s picture

Went ahead and

- tested on D7 with jQuery 1.3 - no difference.
- checked server access logs - lists 1 (actually 2) request(s) for js/admin_menu/cache/[hash]
- commented out the gzip compression part - no difference.

Offtopic, but interesting: Those requests take 450-500ms each on D7; that's even more than D5 and D6.

I'll test your admin_menu_cache module now, but I don't think it will behave differently. Also, could you try this patch yourself?

markus_petrux’s picture

hmm... one significant difference between your patch and my mini module is that mine performs dynamic replacements (*) of the user name and online users counts. Maybe you're getting several hits because the online users counts changes?

(*) This feature was preventing me from testing your patch. But anyway, I'll try it and see what happens on my env.

sun’s picture

Hrm. Actually, I checked the access log when testing jQuery 1.3 in D7. In D6, I see no requests in the access log. So it somehow seems to work - but I don't understand why it takes so much time to load the content from the browser cache.... I mean, when surfing in the system, I can see that admin menu is injected after some time - so the time reported by Firebug's Console seems to be the actual time.

markus_petrux’s picture

Well, I just tested your patch, and seems to work here. And I can see no difference in response times between yours and mine on my system. Only tested the under D6.

It would be nice if someone else could try this patch, and if it can be confirmed it works, maybe then we can start playing with next step, that I would love it was dynamic replacements, starting by the replacements of the user name and online users counts that are currently cached with the HTML of the menu itself.

BTW, in your patch I would still apply the fixes in #20 above.

markus_petrux’s picture

hmm... also noticed that you're prepending the HTML of the ajax response to the body. Maybe prepend costs more than append in terms of DOM recalculations of the browser engine? Curiously enough, your first patches were also appending the response. Any reason on why you switched to prepend?

sun’s picture

Status: Needs work » Needs review
FileSize
12.97 KB

I've tweaked the implementation even further now.

- Page output no longer flickers/jumps.

- Added Drupal.admin.getCache() as a preparing step for #293768: Allow to enable/disable menu additions

- Added initialization of settings to reduce the amount of conditional code.

Remaining todos:

+    // @todo Clean URLs required for JS callback handler support.
+    url: Drupal.settings.basePath + 'index.php?q=js/admin_menu/cache/' + hash,
+  // 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->admin_menu_hash) && strpos($_GET['q'], 'js/admin_menu/cache') !== 0) {
+    return;
+  }
+  // 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'])) {
+    // @todo Hash needs to contain uid and language (just like cid).
+    user_save($user, array('admin_menu_hash' => md5($content)));
+  }
+  // @todo Find a proper cache lifetime.  Infinite, i.e. 1 year?
+  header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 86400) . ' GMT');
+  header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
+  header('Cache-Control: max-age=' . 86400);
+  header('Content-Length: ' . strlen($content));

But also: Make it work in D7.

We are almost done. I want to hear proposals for the remaining todos. Of course, please also test.

sun’s picture

@markus_petrux:

1) So you get the same reponse times I mentioned earlier or do they differ for you?

2) I did not adapt the code for the way ahah.js works, because I want to see a bug report first. Looking at ahah.js tells me that it is performing a different request, using a different dataType. Also note that I changed the dataType to 'text' in my latest patch, because 'html' would automatically search for containing SCRIPT tags in the response.

3) There is no difference in processing time between prepend() and append(). I changed it to prepend, because the entire menu was positioned at the bottom of the window in IE. Not sure whether that still happens now though.

4) Dynamic replacements and on-demand loading of additions is still a different issue. Currently, the menu is not updated at all, even in cases where it should (menu rebuild). That is another issue I forgot to mention in my last follow-up.

sun’s picture

Eliminated some of the todos. The new basePath JS setting is what I actually want to see in core. Drupal.settings.basePath is almost always wrong in its current state.

Remaining todos:

1) Use an own storage for hashs. As visible in this patch, hashs need to be keyed by uid, language, and possibly site (on the server-side). We could use an own table 'admin_menu_hash' using optimized integer columns for those keys, or we could use a cache table, which would allow for flexible key names and would also make sure that users are not sharing this table across multi-sites (i.e. like any other cache table).

2) Avoid separate AJAX requests for clients that don't have a cache (or disabled cache).

3) Ensure the hash properly changes when the menu is rebuilt. (Not sure if this is still an issue though.)

4) Ensure proper styling and behavior in IE. (Not sure if this is still an issue though.)

5) Ensure this works in D7.

sun’s picture

quicksketch requested to commit the clean-ups separately.

sun’s picture

FileSize
8.52 KB

Help. I'm a bit lost with D7.

access log shows for each request:

"GET / HTTP/1.1" 200 6243
"GET /sites/all/modules/admin_menu/admin_menu.js?7 HTTP/1.1" 304 -
"GET /js/admin_menu/cache/52995beb65c2f73c3299dd3e441bd2ec HTTP/1.1" 200 1394
"GET /js/admin_menu/cache/52995beb65c2f73c3299dd3e441bd2ec HTTP/1.1" 200 1394

Note that there are even 2 requests for the cached menu per request.

The only difference to D6 seems to be those two lines in the response header:

Keep-Alive	timeout=5, max=98
Connection	Keep-Alive

I have no idea why this happens. Attached patch is for D7.

Related, but not tied to this D7 issue:

- http://www.mnot.net/blog/2006/05/11/browser_caching mentions that IE(6) only caches very certain documents.

- http://betterexplained.com/articles/how-to-optimize-your-site-with-http-... mentions using an ETag as further possibility.

sun’s picture

New patch for D6, fixing a basePath issue on sites using clean URLs, language path prefixes, and/or having Drupal installed in a subdirectory.

domesticat’s picture

FileSize
16.95 KB

My testing with IE and Firefox this morning seems to point to a performance increase. I made a point to look at the menu options afterward. I noticed 'run updates' was duplicated three times. I have no idea if this was a problem in prior versions; I'd never spotted it before. Screenshot attached.

sun’s picture

@domesticat: That is possible if you upgraded from 6.x-1.1. This bug was fixed in 6.x-1.2. You just need to flush the "Administration menu" cache to remove the duplicates (once).

Thanks for testing! Can you concurrently look into your web server's access log to make sure that subsequent requests do NOT hit the web server? (i.e. the menu is served from your browser's cache)

markus_petrux’s picture

@sun #40: For those requests to the admin menu... there is no request to a site page in between, so it seems there were 2 ajax requests to the same thing in one page? Is is possible that something in D7 is making the javascript code perform 2 ajax requests?

sun’s picture

New patch for D7, accounting for the new settings argument for Drupal behaviors.

Double requests only happen occasionally now, and, even with Drupal core JavaScripts, such as the timezone auto-detection on user/1/edit. This means this is a core bug, so we just don't care (in this issue).

sun’s picture

WorldFallz’s picture

Not exactly sure how I should test this, but installed with patch from #41 on d6.10 with FF3.0.8 and IE7.0.5730.11. Other than a slight delay for the menu to appear (the page loads rights away, the admin_menu a fraction later). It seems to be working fine so far. Definitely feels "snappier".

sun’s picture

How to test this patch:

1) Apply the patch.
2) Clear your site's cache.
3) Clear the cache of your browser(s).
4) Visit a bunch of pages on your Drupal site, just "surf" around - but avoid admin/build/modules ;). If you have Firebug or similar add-ons installed, you should see a separate AJAX request to js/admin_menu/cache/... on each page you visit.
5) Open your web server's access log. Confirm that there is only *one* entry containing js/admin_menu/cache/...

So far, I was able to test Firefox 3.0, IE6, and Opera; all on WinXP.

WorldFallz’s picture

That's basically what I did-- though I'm not sure i have access to the apache logs where this is installed. I'll check and report back if I do.

WorldFallz’s picture

Actually I do have access and there's only one entry as expected. ;-)

sun’s picture

Status: Needs review » Needs work

Great! Thanks for testing.

Well, I'm silently using this nice thang on a few production sites already (because I know what I need to clean up later) - so, yes, we are very close to completion. :)

Remaining todos:

1) Add a cache_admin_menu table to store hashes. Cache keys consist of uid and language. (Cache schema is re-usable, see system.install)

Deferred to separate issues:

- Avoid separate AJAX requests for clients that don't have a cache (or disabled cache). (Failsafe measures)

- Ensure the hash properly changes when the menu is rebuilt. I am absolutely not happy with the current implementation, and after tinkering intensively about it, we should really rebuild whenever hook_menu() is called. AFAIK, menu_rebuild() is (almost?) always called at the end of system-changing request, so we just need to ensure the _next_ request uses a new hash (i.e. truncate the cache table).

- Create a core patch for the new basePath JS setting. (IMHO, this is a bug fix)

sun’s picture

Status: Needs work » Needs review
FileSize
10.88 KB

This time with new cache_admin_menu table and low-level functions.

Hopefully last time "needs review". If anyone has any considerations or objections then speak up - now.

sun’s picture

Status: Needs review » Fixed
markus_petrux’s picture

Thank you sun for your commitment in this issue. Sorry for not having been testing your latest patches, as I'm pretty busy, but it all was looking pretty exciting indeed. :-D

Dave Reid’s picture

sun if it's ok with you, I'm going to remove the 'description' stuff in the update function. It's only needed/used in hook_schema(). :)

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

markus_petrux’s picture

I know this is closed, but just for the record...

Here's a jQuery plugin that encapsulates several methods of client-side storage, with support for several engines, including DOM Session storage, MS userData, Flash, Google Gears, ...

http://eric.garside.name/docs.html?p=jstore

PS: I discovered this plugin from a recent post in the jQuery blog. It's April 17, and that's why I post this now. I found it pretty interesting, and it might be of intereset to someone that lands here searching, etc.

sun’s picture

Interesting! I'd definitely be (very) open to re-visit other client side storage methods at some point in the future.