Phenomenon
Safari 18.6 (and related WebKit builds) introduced a regression where the internal CSSSelectorList::makeJoining method creates extreme performance bottlenecks. When a contenteditable editor triggers a style change (like adding a focus class or a highlight span), the engine attempts to rebuild invalidation sets for attribute selectors. On pages with large CSS frameworks using attribute selectors, this process scales quadratically, causing the UI to freeze during active typing.
Reproduction Steps
- Navigate to a page with a very large CSS file (e.g., a complex Tailwind or CSS-in-JS setup with >10,000 attribute rules).
- Use an editor that dynamically applies classes to the
contenteditablecontainer or its parents upon focus or input. - Observe the latency between a key press and the character appearing on screen.
- Profile the performance using Safari Web Inspector; look for
AttributeChangeInvalidationorRuleSetbuilding operations.
Observed Behavior
- Severe Typing Lag: The main thread blocks for several hundred milliseconds (or seconds in extreme cases) for every event that modifies the DOM attributes.
- Micro-Freezes: Scrolling while the editor has focus also triggers layout invalidations that feel “choppy.”
Impact
- Non-Performant UX: Users perceive the application as heavy and unresponsive.
- Failure in Complex Apps: High-end CMS or IDE-like web editors become unusable because they rely on heavy CSS themes.
Browser Comparison
- Safari 18.6: Exhibits $O(n^2)$ complexity regression.
- Chrome / Firefox: Implement more efficient invalidation bucketing for attribute selectors, maintaining $O(n \log n)$ or better performance.
References & Solutions
Mitigation: CSS Rule Flattening
Avoid using deeply nested attribute selectors (e.g., [class*="..."]) globally if they are not needed. Flattening rules into simple class names can bypass the expensive joining logic.