Problem/Motivation

Not sure if this is a bug or something local. If others can verify it on mac in safari, we can rule it out. But I just installed a fresh Drupal 11 (11.3.6) and whenever clicking on a "display builder" link or button, the browser completely freezes. Nothing happens, even developer tools don't work anymore. In Chrome everything works perfectly.

Running this on ddev, Php 8.2, macbook pro M2 32Gb Tahoe 26.5, Safari 26.5.

When I enable xdebug, with a breakpoint in index.php, the htmx calls aren't even coming in.

Steps to reproduce

Install D11 (normal install with articles, pages)
Install display builder module and enable it.
Enable display builder in the view modes of for example page content type.
Goto the display builder of that view mode.
Browser locks up.

Proposed resolution

-

Remaining tasks

Figure out what is going on inside safari (difficult, because no network dev tools are working)

User interface changes

-

API changes

-

Data model changes

-

Command icon 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

kriboogh created an issue. See original summary.

pdureau’s picture

Thanks you @kriboogh

Here is a related conversation with https://www.drupal.org/u/dasginganinja :

Hi All. I'm building a new platform for the University I work at with SDC components and have landed on Display Builder for our needs as it has a fantastic intersection with our goals with hooking SDC up to manage display. Been watching this project with :heart_eyes:

One question I had is why is display builder so slow to load its display builder? It's c r a w l i n g. I _just_ did a quick test in Chrome. It works as intended.
So something is a very odd on the Apple side of things still. I'll have another coworker check later this week to confirm but wanted to at least bring it up.

Let me get you what I can here. Safari Version 26.4 (21624.1.16.11.4)

I opened the same URL after updating to the latest dev branch (9f30f64) , running updates, and clearing cache. That loaded the base Display Builder in about 45 seconds in safari -- big improvement.

When clicking on the single component row, safari also seems to hang. I observed this behavior in 1.0.0-beta4 as well as current dev.

Additionally, the drush updb failed on display_builder_post_update_1 . The new ContentEntityType migration couldn't deserialize 43 old instance records from State API (__PHP_Incomplete_Class). Not sure if that was something on my end or not though.

Chrome works flawlessly. I'm glad to have found this pain point though as that could affect larger adoption. But there are workarounds

I made sure to validate that and also clear out local storage in my testing when I was in my normal profile.

I changed theme over to Olivero for admin and content editing.  Cleared caches + local state. Same behavior in Safari with 1.0.0-beta4 as the previous video on this module version.

It honestly feels like the main thread is blocked on something causing everything else to grind. I haven't had luck getting a good profile for this.

I have htmx-sse local, per the instructions. I moved it local after having the issues initially and thought it was related to the CDN version.

I have Shoelace local as well.

pdureau’s picture

Looking at @kriboogh browser when he was testing Drisplay Builder on Safari, I got the impression the display builder page was loading right, but was not replaced in the browser window. It seems there are some check in between the page load and the page rendering, and Safari is struggling with one of them.

We removed our Javascript files from the module codebase, the same. Maybe it is is something in the HTML markup itself.

pdureau’s picture

Title: Display builder (safari only issue) » Slow page load with Safari browser
pdureau’s picture

Status: Active » Postponed (maintainer needs more info)

What is happening if we remove everything from components/display_builder/display_builder.twig ? If the slowness goes away, it would be interesting to add the parts one by one until we found the one which is causing slowness.

Someone with Safari can do the test?

kriboogh’s picture

Been trying all day to debug it, but even xdebug gets stuck :/ What I managed to figure out is that if you disable the 'view_main', in ProfileViewBuilder, the page at least loads again.

    return [
      'view_sidebar_buttons' => $view_sidebar_buttons,
      'view_main_tabs' => $view_main_tabs,
      'view_sidebar' => $view_sidebar,
      'view_main' => [], //$view_main,
    ];
kriboogh’s picture

Bit deeper, it's the 'layers' island. If you leave that out, page loads normal.

pdureau’s picture

Status: Postponed (maintainer needs more info) » Active
Bit deeper, it's the 'layers' island. If you leave that out, page loads normal.

Thank you so much

just_like_good_vibes’s picture

Hello,
please check the keyboard shortcut, which may to be the reason of the problem.

pdureau’s picture

Thanks Mikael.

Indeed, removing LayersPanel::keyboardShortcuts() "fixes" the problem.

For information, IslandInterface::keyboardShortcuts() is called by ProfileViewbuilder::buildDynamicTabs() which is generating those data attributes:

        $attributes['data-keyboard-key'] = $keyboard['key'] ?? '';
        $attributes['data-keyboard-help'] = $keyboard['help'] ?? '';
        $attributes['aria-keyshortcuts'] = $keyboard['key'] ?? '';

then, keyboard.js is listening keydown event and move the focus to the corresponding element:

 document.addEventListener('keydown', (event) => {
            ...
            const key = event.key;
            ...
           const element = document.querySelector(
              `[data-keyboard-key="${key}"]`,
            );
            ...
            element.click();
            element.focus();
 }

Where is Safari struggling? it is on load, so the keydown Javascript hasn't been executed yet.

Is it aria-keyshortcuts="y" ?

kriboogh’s picture

I had Claude AI have a go at this. After applying it's suggested solution, now the page loads again although still slow. Before it wouldn't load at all. So there might be something else still involved.

Here's the complete Claude AI Diagnosis:

The freeze is not the keydown handler — it is the sl-show handler in assets/js/keyboard.js:41-52. It has three defects that together produce a synchronous re-entry loop on Safari at initial page load, especially when the Layers island is present (which adds more Shoelace tooltip surface inside the same builder container).

  1. Overly broad target check (line 42). event.target.querySelector('[data-island-action="help"]') returns truthy whenever the help button exists anywhere in the subtree of the element that fired sl-show, not only when the fired element is the help tooltip. On load, several Shoelace components inside the builder emit sl-show (tooltips on toolbar buttons, dropdowns, dialogs, drawers). The guard fails to filter them out.
  2. Wrong tooltip selection (line 45). event.target.querySelector('sl-tooltip > div') returns the first inner
    in the subtree. In the toolbar this is typically the highlight or fullscreen tooltip, not the help tooltip. Writing keyboard help markup into it is a bug, and the mutation causes Shoelace to react.
  3. Innerhtml mutation triggers Shoelace slotchange → repositioning → possibly another sl-show. Shoelace tooltips reflow when their content slot changes. On WebKit, the tooltip's anchor recomputation with the newly mutated content can emit another sl-show when the tooltip's popup is re-attached to the DOM (Safari re-fires the event on shadow-DOM host attachment). That re-fires the builder-level listener, and step 1 repeats.
  4. Chrome/Firefox do not always re-fire sl-show after a slot mutation, which is why the freeze is Safari-specific.
  5. The user said "when the Layers island is enabled" — that fits because Layers is a large view island whose panel contains many <sl-card> / <sl-details>-based db-layer elements. When the Layers panel opens, all of those Shoelace components can emit sl-show as they mount. Each one bubbles to builder, the handler runs, mutates a tooltip, that mutation cascades — loop.
  6. Proof / cross-check

    • The // @todo add debounce comment on line 101 is next to the keydown handler, showing that the module author already knew there's a re-entry problem in this file. The same class of bug exists in the sl-show handler above, but was never guarded.
    • Every other sl-show listener in the module has a very tight event.target?.id === or event.target.id === 'db-first-drawer' guard (see db_drawer.js:275, db_drawer.js:290, db_drawer.js:389, theme_menu.js:68) — the keyboard.js one is the odd one out because it uses a descendant query instead of an identity check.
    • keyboard.js is loaded via display_builder:display_builder (the top-level component), so the listener attaches for every builder. When Layers island renders, more Shoelace elements exist under .display-builder — so the "matching descendant" grows, and the frequency of sl-show bubbling matches increases dramatically.

    Possible Fix

    Tighten the sl-show handler so it only reacts to the help tooltip firing its own sl-show, and does not mutate DOM inside other tooltips. Two safe minimal changes:

    1. Restrict to the exact help element by identity, not descendant match:
      builder.addEventListener('sl-show', (event) => {
        // Only react when the help tooltip itself opens.
        const helpButton = event.target.closest?.('[data-island-action="help"]')
          || event.target.querySelector?.(':scope > [data-island-action="help"]');
        if (!helpButton) return;
      
        const helpTooltip = helpButton.closest('sl-tooltip')
          || helpButton.querySelector('sl-tooltip');
        if (!helpTooltip || !helpTooltip.contains(event.target)) return;
      
        const helpKeyboard = helpTooltip.querySelector(':scope > div[slot="content"], :scope > div');
        if (!helpKeyboard) return;
      
        const nextHTML = `<ul class="db-keyboard-help">${Object.values(keyboardHelp).map((item) => `<li>${item}</li>`).join('')}</ul>`;
        if (helpKeyboard.innerHTML === nextHTML) return; // idempotent guard, cuts the loop
        helpKeyboard.innerHTML = nextHTML;
      });
    2. At minimum, an idempotent write (the smallest possible patch that will stop the freeze in Safari today):
      const nextHTML = `<ul class="db-keyboard-help">${...}</ul>`;
      if (helpKeyboard.innerHTML !== nextHTML) {
        helpKeyboard.innerHTML = nextHTML;
      }

      The idempotence check means the second and subsequent bubbled sl-show events do not mutate the DOM, so Shoelace does not react and the loop cannot sustain.

    3. Optionally, ignore non-trusted / retriggered events at the top of the handler:
      if (!event.isTrusted && event.target === lastTarget) return;
      Combining #1 and #2 is the robust fix. #2 alone is enough to stop the current Safari freeze without changing behavior for other browsers.

    Verification steps

    • In Safari, load a page with a .display-builder that has the Layers island enabled. Freeze should reproduce with current code.
    • Apply the idempotent-write guard in assets/js/keyboard.js.
    • Reload. Page should load normally. Hovering the help button should still show the keyboard shortcuts list including the y binding for Layers.
    • Rerun Playwright: tests/src/Playwright/Tests/layers.spec.ts and tests/src/Playwright/Tests/toolbar.spec.ts should pass unchanged.
kriboogh’s picture

So with this patch I got the stall halved. Initially it stalls for about 1m30 seconds. There are 2 waves. From the moment you instruct Safari to load the page, 40s laps where the page is blank, then the page is drawn, but frozen. After another 40s the page is interactive. and after 1m30 the safari considers the page fully loaded.

When applying the attached patch AND also you need to load the shoelace library as a full js locally (not in chunks, not from CDN) the page is completly loaded after 40s. (still a long time, but some progress).
The patch is what is explained above by Claude AI.

kriboogh’s picture

Finally found the real reason. The culprit was layers.css. It has to many nested & rules which safari can't handle. When expanding them, one rule got over 115k characters. Attached is a (AI generated) patch of layers.css that solves the problem. Please verify on your behalf if this thing still does the intended behaviour.

Safari 26 CSS-engine regression workaround. The 10-deep & & & & & & & & & & nesting is technically valid CSS, but crashes Safari 26. The workaround (flat descendant selectors) is functionally identical. Worth reporting to WebKit separately (bug in Safari), but the display_builder patch is a pragmatic fix while WebKit ships a fix.

I tested this on a fresh install, and applying both patches loads the page now instantly.

kriboogh’s picture

PS: I intentionally did not create a MR yet, as there are two issues actually solved here (2 patches) and maybe we need to pull them apart or not. I'll leave that upto you to decide Piere.

pdureau’s picture

Thanks a lot, that's impressive. Let's do a single MR with the 2 fixes

pdureau’s picture

Status: Active » Needs work
Issue tags: +display_builder-1.0.0-beta

Don't forget to assign the ticket to you :)

Once the MR is rebased and the stylelint has been run. You can unassign it and switch to "Needs review" status

kriboogh’s picture

Assigned: Unassigned » kriboogh
kriboogh’s picture

Assigned: kriboogh » Unassigned
Status: Needs work » Needs review

MR ran, no more issues. Pls review.

pdureau’s picture

Assigned: Unassigned » mogtofu33
mogtofu33’s picture

Parent issue: » #3576683: [meta] UX refresh
mogtofu33’s picture

Assigned: mogtofu33 » Unassigned
mogtofu33’s picture

Version: 1.0.0-beta5 » 1.0.x-dev

Layers.css tree removed and keyboard rewamped in #3576683: [meta] UX refresh.

Need a review with 1.0.x-dev.

pdureau’s picture

Status: Needs review » Fixed

;Checked with Kris, we can close this. Thanks everybody.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

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