Updated: Comment #65
Problem/Motivation
Drupal 8 will ship with far more JavaScript. Drupal 8 will need to perform well on mobile.
This means we must allow JavaScript code to apply smart client-side caching strategies in order to provide a fast experience. Two of the most obvious things to cache are: 1) user-specific data, 2) permission-dependent data.
Currently, Drupal 8 performs 2 additional HTTP requests per page load that hit Drupal:
- 1 HTTP request/page load for rendering contextual links
- 1 HTTP request/page load for retrieving in-place editing metadata
(Assuming the user has permission to both use contextual links and in-place editing, of course.)
Both of these HTTP requests are necessary because the necessary metadata cannot be embedded in the page itself, because that would break render caching.
However, they result in:
- worse front-end performance
- more networking activity (hence reduced battery life)
- worse site scalability (because they each require Drupal to bootstrap)
#2005644: Use client-side cache tags & caching to eliminate 1 HTTP requests/page for in-place editing metadata, introduce drupalSettings.user.permissionsHash gets rid of the 1 HTTP request/page load for retrieving in-place editing metadata. This issue gets rid of the one for rendering contextual links.
Proposed resolution
Use the same approach as the one in #2005644: Use client-side cache tags & caching to eliminate 1 HTTP requests/page for in-place editing metadata, introduce drupalSettings.user.permissionsHash: depend on drupalSettings.user.permissionsHash.
However, more care is needed in this case, since contextual links for entities may vary depending on the state of the entity. So, the contextual links may only be cached for as long as the entity remains unchanged (identical EntityChangedInterface::getChangedTime()).
Remaining tasks
Handle entity changes.
User interface changes
None.
API changes
- Internals change: contextual links are rendered without the
?destination=<current_page>parameter, this is added in JS.
| Comment | File | Size | Author |
|---|---|---|---|
| #25 | client_side_caching_contextual-2136507-25.patch | 17.26 KB | wim leers |
Comments
Comment #1
wim leersBlocked on #2005644: Use client-side cache tags & caching to eliminate 1 HTTP requests/page for in-place editing metadata, introduce drupalSettings.user.permissionsHash (to provide
drupalSettings.user.permissionsHash). Initial patch spliced from that issue's patch.Comment #2
wim leersI think that conceptually, it makes a ton of sense to rely on
EntityChangedInterface::getChangedTime(). That still leaves two problems:data-contextual-entity-last-changedattribute, or something else?The only way I see we can do this without introducing API changes is to make every entity type that adds contextual links to its entities, also set
#contextual_links's'metadata'key toarray('changed' => $entity->getChangedTime()). That immediately resolves the problem.The only downside: the client-side cache entry will continue to exist indefinitely, since an updated entity results in an updated cache key. However, since
sessionStorageis used, it will only continue to live for the duration of the tab remaining open, so there's only a minor cost.Comment #3
wim leersReroll against HEAD. Most things remained the same. But with #2141055: When multiple instances of the same entity on one page, only the first can be edited and #2075185: When an entity is in-place edited (i.e. saved), other instances of that entity on the same page are not updated (no propagation) having landed, I had to improve a few things in Edit's JS to prevent race conditions while using the client-side cache.
Comment #5
wim leersComment #6
wim leers5: client_side_caching_contextual-2136507-5.patch queued for re-testing.
Comment #8
webchickLazy testbot.
Comment #9
wim leersStraight reroll.
Comment #11
wim leersComment #12
moshe weitzman commentedWhere could I learn what _.chain(storage) means? I get that it refers to client side storage api but the syntax is unfamiliar to me.
This issue could use some better js review than me.
Comment #13
wim leers#12: that's underscore.js. The "chain" just means "let me call multiple collection methods in a row, i.e. don't yet return a hash". That's why I can call
keys()followed immediately byeach(). i.e. these are equivalent:and
Comment #14
wim leersAFAICT this would also solve #607244: Decrease performance impact of contextual links.
Comment #15
dawehnerIsn't it a nice feature to get redirected after changing a block?
Yes, permissions could work, though routes can have arbitrary access checking
I don't see a return statement in that function.
Comment #16
penyaskito1. Destination is added client-side now. See function initContextual ($contextual, html).
Comment #17
wim leers#15.1: answered by #16 — thanks penyaskito :)
#15.2: I know, I agree. That was also catch's concern in #2005644. The solution I proposed here is to cache on the client side based on TWO things: permissions AND the last changed time of the entity. As I said in the issue summary:
That still won't help if the arbitrary access checks depend on external factors like temperature, time of day, or whatnot, but it will be sufficient to handle e.g. workflow status.
#15.3: good catch, thanks!
Comment #18
moshe weitzman commentedOK, thanks for the js education and for the better doxygen. Ready to fly.
Comment #19
webchickThis patch looks catch-iferous!
Comment #20
dawehnerOh thank you ...
Well, doesn't that basically mean we introduce a new problem here? Given that this issue is major this could be okay for sure, but it would be at least good to know whether we have a long term plan to allow people in contrib to swap things out if needed.
Comment #21
catchWe have #2099137: Entity/field access and node grants not taken into account with core cache contexts open - not for client side caching, but via that issue it will be possible to add something like a time-of-day granularity to entity render cache IDs. I'd expect to then be able to do the same for the client side cache ID. It might not be very clean, but I don't think we're preventing the temperature access module from being written, just making it a bit more complex (which is fine IMO - not many sites/modules need time of day/temperature based access).
Comment #22
wim leersLike catch says, you can do the same for client-side cache IDs: just add more metadata than that what's already being set.
Furthermore: it's cached client-side in
sessionStorage, notlocalStorage. That means it's cached on a per-browser tab basis. In other words, just like PHP doesn't allow you to mess things up too badly, neither will this client-side caching.Comment #23
catch17: client_side_caching_contextual-2136507-17.patch queued for re-testing.
Comment #25
wim leersStraight reroll. This was broken by #1649780: Remove first/last/odd/even classes in favor of CSS3 pseudo selectors.
Comment #26
catchCommitted/pushed to 8.x, thanks!
Comment #27
wim leersAwesome, thank you! :)
Comment #28
wim leers