Generated with the help of an LLM, reviewed and tested by a human.
Problem/Motivation
Since 11.2.0, on sites using the Toolbar module, the contextual links "Edit" toggle is shown in the toolbar on every page for users with the access contextual links permission, including pages that have no contextual links at all, such as /admin/config. Clicking it on those pages does nothing except toggle its pressed state. Before 11.2.0, the toggle was hidden when a page had no contextual links.
Reproduced on a clean 11.x-dev install (commit 18d8ceb4) and on 11.3.19. The file is identical in 11.2.0 through 11.3.19, 11.5.0-beta1 and 11.x. The contrib Toolbar project (2.x) contains the same code.
Steps to reproduce
- Install 11.x with the Standard profile.
- Uninstall the Navigation module and install the Toolbar module (Standard installs Navigation on 11.x).
- Install Olivero and set it as the default theme, so front-end pages have blocks with contextual links.
- As the administrator, visit
/user/1. The page has contextual links and the "Edit" toggle is shown, as expected. - Visit
/admin/config(or/admin/people,/admin/structure,/admin/content). The page has no contextual links, but the "Edit" toggle is still shown. See the attached screenshot. - In the browser console on that page,
Drupal.contextual.instances.countreturnsundefinedandDrupal.contextual.instances.lengthreturns0.
Expected: the toggle has the hidden class on pages without contextual links.
Actual: the toggle is shown on every page.
Proposed resolution
- Read
Drupal.contextual.instances.lengthinstead of.count. - In
updateVisibility(), computethis.isVisible = this.contextualCount > 0and apply it withthis.$el[0].classList.toggle('hidden', !this.isVisible). - Have
render()callthis.updateVisibility()instead of togglinghiddenitself, so the first render is correct when contextual links already exist.
User interface changes
The "Edit" toolbar toggle is hidden again on pages without contextual links.
| Comment | File | Size | Author |
|---|---|---|---|
| #3 | 3632476-contextual-edit-toggle-11.3.x-2.patch | 2.67 KB | rbrandon |
| #2 | core-contextual-toolbar-edit-toggle-visibility.patch | 2.08 KB | rbrandon |
| image-1792683499620.png | 24.52 KB | rbrandon |
Issue fork drupal-3632476
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
rbrandon commentedWe are using the following patch for 11.3.19, I will open an MR off 11.x with similar code and test coverage.
Comment #3
rbrandon commentedUpdated 11.3.x patch to fix toggling bug hidden by old failure. Fixes manageTabbing() so it only limits tabbing while the toggle is visible.
Comment #5
rbrandon commentedMR is ready for review with test coverage.
Comment #6
rbrandon commentedComment #7
smustgrave commentedCould the summary be cleaned up to lose some of the AI verboseness please.
Comment #8
rbrandon commentedSure, I did edit a lot and only left what was relevant. I will add some of the info here and refine it further.
Here are some notes on the origin of this bug and why the fix is constructed the way it is.
This is a regression from #3203920, which replaced the Backbone
StateModelandVisualViewwithDrupal.contextual.ContextualToolbarModelView(core/modules/contextual/js/toolbar/contextualToolbarModelView.jsin 11.2.x and 11.3.x, moved unchanged tocore/modules/toolbar/js/views/ContextualToolbarModelView.jsby #3618746). The port has three bugs that combine:Drupal.contextual.instancesis aProxyover an array with nogettrap, but the class readsDrupal.contextual.instances.countin four places, which is alwaysundefined. ThecontextualCountsetter only callsupdateVisibility()when the value changes, so it is never called.updateVisibility()callsthis.get('contextualCount'), a BackboneModelmethod that does not exist on the new class. Bug 1 hides this; fixing bug 1 alone makes it throwTypeError: this.get is not a function.render()callsthis.$el[0].classList.toggle('hidden', this.isVisible), but the BackboneVisualViewusedtoggleClass('hidden', !this.model.get('isVisible')). BecauseisVisiblestaysfalse, every render removes thehiddenclass thatContextualHooks::toolbar()renders the tab with.In addition,
updateVisibility()only setsisVisibleand nothing re-renders afterwards (the Backbone view re-rendered onchange), so the toggle would not update when contextual links are added after the toolbar initializes.Fixing any of these on its own makes things worse: fixing 1 alone throws in 2, and fixing 1 and 2 together hides the toggle on exactly the pages that have contextual links.
#3611072 reports the opposite symptom on 10.4+ (the toggle is hidden on cached page loads). It concludes that 11.x is unaffected because the new constructor counts existing links, but that count is the always-undefined
instances.count; 11.x only appears fixed because the toggle is never hidden. Comment #18 on #3535051 also mentions bugs 1 and 2 as out of scope there.Comment #9
rbrandon commented