-
Notifications
You must be signed in to change notification settings - Fork 11
Fix multi-second nav freeze caused by html:has() CSS amplification #697
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
patcapulong
wants to merge
3
commits into
main
Choose a base branch
from
pat/authentication-page-freeze
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+171
−20
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
b804ae9
Fix multi-second nav freeze caused by html:has() CSS amplification
patcapulong ca65e3e
Address Greptile review: restore guarded MutationObserver, fix comment
patcapulong 248b993
Neutralize Mintlify's bare-:has() code-block rules (second freeze sou…
patcapulong File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| // Rewrites three of Mintlify's code-block CSS rules whose selectors start | ||
| // with a bare `:has(` — an unanchored subject that, in Chrome/Blink, | ||
| // permanently flags the document root as ":has-affected" the first time a | ||
| // matching code block renders. From then on, every forced style recalc in | ||
| // the tab walks the entire document (~30x slower), which turns mermaid's | ||
| // ~280 getBBox measurements during navigation into a multi-second freeze | ||
| // (hover effects dead; scroll survives on the compositor thread). | ||
| // | ||
| // In Mintlify's DOM the ":has() parent" is always expressible as a preceding | ||
| // sibling instead, which Blink matches cheaply. Verified pixel-identical and | ||
| // computed-style-identical across all Global Accounts pages, light + dark. | ||
| // | ||
| // Rules are only touched on an exact selector match, so if Mintlify ships | ||
| // different CSS this becomes a no-op — the look can never break; worst case | ||
| // the perf fix silently stops applying. | ||
| // | ||
| // Coverage caveat: custom JS runs after first paint, so a tab whose FIRST | ||
| // page is one that renders standalone code blocks (e.g. implementation- | ||
| // overview) is poisoned before this runs, and that can't be undone. All | ||
| // pages rendered after this script runs (every SPA navigation) are safe. | ||
| // The complete fix is Mintlify correcting these selectors upstream — remove | ||
| // this file when they do. | ||
| (function () { | ||
| var MAP = { | ||
| ':has([data-floating-buttons]) > [data-component-part="code-block-root"] pre > code': | ||
| '[data-floating-buttons] ~ [data-component-part="code-block-root"] pre > code', | ||
| ':has([data-component-part="code-block-header"]) > [data-component-part="code-block-root"] pre > code': | ||
| '[data-component-part="code-block-header"] ~ [data-component-part="code-block-root"] pre > code', | ||
| ':has([data-component-part="code-group-tab-bar"]) [data-component-part="code-block-root"] pre > code': | ||
| '[data-component-part="code-group-tab-bar"] ~ [data-component-part="code-block-root"] pre > code, ' + | ||
| '[data-component-part="code-group-tab-bar"] ~ * [data-component-part="code-block-root"] pre > code', | ||
| }; | ||
|
|
||
| function patchRules(owner) { | ||
| var list; | ||
| try { | ||
| list = owner.cssRules; | ||
| } catch (e) { | ||
| return; // cross-origin sheet | ||
| } | ||
| if (!list) return; | ||
| for (var i = list.length - 1; i >= 0; i--) { | ||
| var rule = list[i]; | ||
| if (rule.cssRules && rule.cssRules.length) { | ||
| patchRules(rule); // @media / @layer / @supports groups | ||
| continue; | ||
| } | ||
| var sel = rule.selectorText; | ||
| if (!sel || sel.indexOf(':has(') === -1) continue; | ||
| var parts = sel.split(','); | ||
| var out = []; | ||
| var hit = false; | ||
| for (var j = 0; j < parts.length; j++) { | ||
| var trimmed = parts[j].trim(); | ||
| if (MAP[trimmed]) { | ||
| out.push(MAP[trimmed]); | ||
| hit = true; | ||
| } else { | ||
| out.push(parts[j]); | ||
| } | ||
| } | ||
| if (!hit) continue; | ||
| try { | ||
| var css = rule.cssText; | ||
| var body = css.slice(css.indexOf('{')); | ||
| owner.deleteRule(i); | ||
| owner.insertRule(out.join(',') + ' ' + body, i); | ||
| } catch (e) { | ||
| /* leave the rule as-is */ | ||
| } | ||
| } | ||
| } | ||
|
|
||
| function scanAll() { | ||
| for (var i = 0; i < document.styleSheets.length; i++) { | ||
| patchRules(document.styleSheets[i]); | ||
| } | ||
| } | ||
|
|
||
| // Scan every frame while the page is still settling (idempotent: once a | ||
| // rule is rewritten its selector no longer matches the map)... | ||
| var settleAt = 0; | ||
| function loop(now) { | ||
| scanAll(); | ||
| if (document.readyState === 'complete') { | ||
| if (!settleAt) settleAt = now; | ||
| if (now - settleAt > 2000) return watch(); | ||
| } | ||
| requestAnimationFrame(loop); | ||
| } | ||
|
|
||
| // ...then only re-scan when new stylesheets appear (SPA navigations). | ||
| function watch() { | ||
| new MutationObserver(function (muts) { | ||
| for (var m = 0; m < muts.length; m++) { | ||
| var added = muts[m].addedNodes; | ||
| for (var n = 0; n < added.length; n++) { | ||
| var tag = added[n].tagName; | ||
| if (tag === 'STYLE' || tag === 'LINK') { | ||
| scanAll(); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
| }).observe(document.documentElement, { childList: true, subtree: true }); | ||
| } | ||
|
|
||
| loop(performance.now()); | ||
| })(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.