Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem/Motivation
When viewing views with dropbuttons in them, they are rendered as simple HTML lists, because the necessary JavaScript that adds HTML classes is not added to the page.
The problem is that attachments don't bubble up at the moment.
Proposed resolution
T.b.d.
Remaining tasks
T.b.d.
User interface changes
Change the UI from broken to fixed.
API changes
None.
Beta phase evaluation
Issue category | Bug because it blocks cacheablity in general and leads to a broken UI (no working dropbutton for example). |
---|---|
Issue priority | Critical, as its part of the chain of problems around cacheability and views. |
Disruption | It just provides additions to existing APIs |
Comment | File | Size | Author |
---|---|---|---|
#58 | 2350551_58.patch | 579 bytes | Mile23 |
#54 | 2350551.54.patch | 22.39 KB | arlinsandbulte |
#51 | 2350551.51.patch | 22.39 KB | alexpott |
#51 | 48-51-interdiff.txt | 995 bytes | alexpott |
Comments
Comment #1
dawehnerAfaik the problem is that
Render\Contextual
does not add the library.Comment #2
XanoThis is about dropbuttons, not contextual links ;-)
Comment #3
dawehner@effulgentsia figured out whether the problem is: We do have a drupal_render() call in theme().
Comment #4
Xano#2221433: Clean up views rendering. Move stuff from template_preprocess_views_view(), into a #pre_render callback caused this.
Comment #5
damiankloip CreditAttribution: damiankloip commentedThis is generally a bigger problem, we need to change how fields are rendered in views.
Comment #6
dawehnerLet's rename it a bit.
Comment #7
damiankloip CreditAttribution: damiankloip commentedYep. Pretty much what's happening there :)
Comment #8
catchThis is limited to Views' rendering of fields no?
Comment #9
dawehnerYes it is.
Comment #10
dawehnerComment #11
XanoThis seems to have been fixed by https://www.drupal.org/node/2362887.
Comment #12
damiankloip CreditAttribution: damiankloip commentedI think we could/should still use this to actually work on field rendering, as mentioned above somewhere.
Comment #13
dawehnerThis is currently blocked by #2342287: Allow Twig in Views token replacement
Comment #14
Wim Leers#11: Yep, that is the CR for #2361681: drupal_render(): invert second argument ($is_recursive_call -> $is_root_call) => more strict, better DX/TX, which should indeed fix all bubbling of render metadata (which includes assets). But I'm not 100% certain yet that truly everything is fixed because Views rendering is so incredibly complex. Looking into this now…
Comment #15
Wim LeersHurray! We actually already fixed this… by accident! :)
From #2378789-10: Views output cache is broken:
Comment #16
damiankloip CreditAttribution: damiankloip commentedNo, I don't think we did.... :(
This will work fine for un cached views, but cached views will not keep the correct attachments. Hence comment in #12, we need fields to return render arrays and keep them as that I think.
Comment #17
BerdirNot field rendering but more views cache tags in general: Another problem that I see is that the entity list and views cache tags are only added to the internal views result/query (unless that was changed recently) but not the render array, so they are not bubbling up either.
I tried to enable views caching (I currently have caching enabled on the blocks and not the views) but explodes on serialization issues, as it is serializing the results with the entities, which have a reference to the view, which has all kinds of things...
Comment #18
Wim LeersCan you:
Correct; those are the cache tags associated with the View itself, and more specifically: with its query. But that's out of scope here. I'd think that the Tag-based cache plugin for Views already does this, but you're right that it doesn't. We don't have an issue for that yet.
What kind of view is this?
Comment #19
damiankloip CreditAttribution: damiankloip commentedSo my steps to reproduce this are:
- Enable tag based caching on the admin/people view
- Go to admin/people, the first time you should see this:
- Refresh the view again (this time will be retrieved from cache):
Oh, and of course, make sure that your render cache bin is not set to the NullBackend! :)
Comment #20
Fabianx CreditAttribution: Fabianx commentedAssigning to Wim ...
Comment #21
Wim Leers(I was working on this last week, but then got pulled into other stuff. Posting what I had written so far.)
Reproduced #19.
As we'd expect, the problem is that the asset library for dropbutton is not in the Views output cache item, and hence it doesn't get restored on an output cache hit. The question is
.Comment #22
jibran@Wim Leers posting the patch will help :)
Comment #23
Wim LeersDid the necessary debugging. The problem lies indeed in the way fields in field-based Views (table views, grid views …) are rendered.
ViewExecutable::render()
does "split rendering"The first trickiness is in
DisplayPluginBase::render()
. (Note that it is immediately after invoking this function inViewExecutable::render()
that we store its output in the output cache.) In there, if the result set is not empty, we render the style plugin, which returns$rows
. And then we return a render array which has'#rows' => $rows
but also#attached
. It is that#attached
that we used to determine which attachments to store in the output cache. But … it's incomplete: we should also have been using$e['#rows']['#attached']
, i.e. at the return value ofStylePluginBase::render()
. Because the current code basically ignores any attachments that the style plugin adds.But that turns out to be more difficult than it seems…
FieldPluginBase::theme()
loses bubbleable rendering metadataStylePluginBase::render()
callsStylePluginBase::renderGrouping()
, which callsStylePluginBase::renderFields()
. That function renders fields into$this->rendered_fields
… which means they're not ending up inside the "main render array" for the view, that they're assembled back in there at a later time.And the real problem is that
StylePluginBase::renderFields()
callsFieldPluginBase::theme()
. Inside that function, a render array is generated, but only the output of the rendered render array is kept, i.e. we keep the resulting markup, but we get rid of the render array. Hence the bubbleable rendering metadata (#attached
,#cache
and#post_render_cache
) is lost.Solution
The ideal way to fix this is by "fixing" the Views render pipeline. I say "fixing" between quotes, because it was never really broken. Its only fault is being architected in a time where there was no such thing as bubbleable rendering metadata yet: there were only global statics. We chose to go for bubbleable rendering metadata a long time ago, for many reasons, but we never updated Views accordingly. (Because, let's be honest, that's a very daunting task.) At some point, we should bite the bullet and do this. But a "remove all warts from Views" initiative in the beta phase of Drupal 8 seems like bad timing at minimum.
Therefore, the best solution I can think of, is actually to just leverage an existing wart:
$this->view->element
. This is already being used to contain the attachments of "the rendered view", while it is being rendered. Simply makingFieldPluginBase::theme()
update it is all that's necessary!Comment #25
Wim LeersDiscussed this with dawehner in IRC. He asked for an abstraction, so that Views doesn't need to know about those Render API internals (i.e. similarly to #2379741: Add Renderer::getCacheableRenderArray() to encapsulate which data is needed for caching a render array and have views use it). So, did exactly that in this reroll. Took quite a bit of time to figure out the right abstraction, but I think you'll like this. Because it also allows us to simplify code in several other places :)
(Note to self: patch generated with
-M10%
.)Comment #26
dawehneroh?
Can we open a follow up to use static:: everywhere?
This is really kinda of cleaner, if you ask me.
Don't we partially want to consistent use the static method or not?
DO we still have to do that? Can't we just hide that internally?
Comment #29
Wim Leers(Unpublished #27, which was a duplicate of #26.)
#26
$frame
. Now, with the new helper functions, we generate$frame
. So instead of getting a reference to the top item in the stack, we pop it, and then push the new one.Also fixed the exceptions.
Comment #30
Wim LeersBetter title.
Critical because this blocks #2381217: Views should set cache tags on its render arrays, and bubble the output's cache tags to the cache items written to the Views output cache (which was triaged critical).
Comment #31
Wim Leersdawehner, damiankloip: how would you recommend writing generic test coverage for this? Can you point to tests I can look at as an example?
Comment #32
Wim LeersAdded unit tests for the new logic in
BubbleableMetadata
(formerlyRenderStackFrame
).dawehner, damiankloip: I can't add a regression test before you answer #31. I tried to do it myself, but failed :( Writing tests is easy, creating a test view is not :(
Comment #33
dawehnerI like the general approach.
Can we use
string[]
here?I would drop this here but just keep the todo, this would be more honest. This skip is afaik used for pointless methods.
Note: The issue to unit test drupal_render adds this.
Can we use @return static ?
nope, don't try to readd a self:: call
Comment #34
Wim Leers::tags
. The other two are nested arrays.Comment #35
dawehnerWell, then use
string[][]
Comment #36
Wim LeersI didn't know that was valid syntax. Cool :) Done.
But that's not really correct for
#post_render_cache
: for that key, you're not required to use strings at the second level; the context kan include integers or even objects. Usingarray[]
for that instead then.Comment #37
damiankloip CreditAttribution: damiankloip commentedOverall I think this is looking like a pretty good idea.
Is this still just a 'value object' when it merges data and applies this to other build arrays? Just asking.
Seems weird using the service (abstracted, by 'renderer' definition) then the static hardcoded
Renderer
. This could at least use $renderer->mergeBubbleableMetadata()?Also, does that method need to be static? is there another good reason it is? Makes it seem just hacked into the renderer class.
Do we need to bother with these still in unit tests? @covers says the same really? Not sure what the directive is on that at the moment.
'test data'
Comment #38
Wim Leers\Drupal\Core\Url
,\Drupal\Core\Link
,\Drupal\Core\Access\AccessResult
…Hence I'm not sure it's worth changing here. I could
s/Value object used for…/Object used for…/
, if that helps? :)ElementInfoManagerTest
; fixing it there is probably out of scope here, so leaving the typo there.Comment #39
dawehnerWould be nice if we point to \Drupal\Core\Render\RendererInterface::render
From the pure name, its not obvious for me, which way round it is, can we rename it maybe to something like BubbleMetadata::applyTo($build) ?
From the name I would have also expected that $element is massaged by reference.
Good catch!
Comment #40
Wim LeersFirst, a straight reroll. #2379741: Add Renderer::getCacheableRenderArray() to encapsulate which data is needed for caching a render array and have views use it broke this.
(And generated with
-M10%
again, to make it easier to review.)Comment #41
Wim LeersComment #43
dawehnerThank you!
Added a beta evaluation
Comment #44
Wim LeersAlex Pott remarked that
::merge()
could be changed to return a new object, hence making the object immutable. Let's do that.Comment #45
Wim LeersDone.
Comment #46
dawehnerAlright, seems fair.
Comment #47
damiankloip CreditAttribution: damiankloip commented+1, that does seem fair.
Comment #48
alexpottLet's go one step further and make this thing actually immutable.
Comment #51
alexpottFixing the fail.
Comment #52
dawehnerGood fix.
Comment #53
Wim LeersThanks, those changes look great! Just one nitpick, that can be fixed upon commit:
s/array/string[]/
Comment #54
arlinsandbulte CreditAttribution: arlinsandbulte commentedApplied #53
Comment #55
Wim LeersManually diffed #54 with #51, the interdiff is only what's in #53, so keeping at RTBC :)
Comment #56
alexpottGiven that my changes to this patch only concern internal implementation details of the value object I think it is okay for me to commit this. Committed 3d556b7 and pushed to 8.0.x. Thanks!
Thanks for adding the beta evaluation to the issue summary.
Comment #58
Mile23There's no such method as BubbleableMetadata::apply(), so the @cover annotation is incorrect and I can't make a coverage report.
Comment #59
xjmThanks @Mile23!
Can we file a separate followup for that fix? Generally we don't reopen criticals unless the need a revert. Thanks!
Comment #60
Mile23Ba da bing. #2413941: BubbleableMetadataTest::testApply has wrong @covers