External scripts over 200KB that the parser inserted with defer (or with no attribute) are silently reclassified as async. Defer scripts must run in document order before DOMContentLoaded. Async scripts run whenever. This reclassification is itself a band-aid for slow JS execution, and it breaks the ordering contract sites rely on.
What happens
src/Starling.Engine/ScriptFetcher.cs:233-239 promotes large None/Defer external scripts to Async (the DeferringLargeScript log at line 237 records it, but the page cannot see it). All three x.com bundles qualify: vendor.b6dfc27a.js (689KB), i18n/en.ee2bc0fa.js (665KB), main.565e4e9a.js (1.4MB).
Document order is preserved today only by accident — RunAsyncScripts iterates Fetcher.Scripts in document order (src/Starling.Engine/Engine.cs:1456-1463). Nothing guarantees that stays true.
The bigger problem: even after the DOMContentLoaded-ordering bug (sibling issue) is fixed, this promotion would still push the bundles into the async step, after DOMContentLoaded — keeping x.com's "Something went wrong" watchdog firing.
Fix
This mechanism is a band-aid. It exists so huge bundles do not block the render-critical script phase, because executing multi-megabyte bundles is slow in the Starling JS engine (minutes for x.com). The real root cause is JS execution speed, which is tracked separately (inline caches and hidden-class work exist on an unmerged branch).
Real fix: remove the promotion for parser-inserted defer and ordered scripts and restore spec classification. Keep first paint fast the spec-legal way instead: paint first, then run the defer batch, then fire DOMContentLoaded (the sibling DOMContentLoaded issue describes that pipeline). DOMContentLoaded then arrives later than Chromium's by the execution time. That is honest, order-correct degradation under a slow engine — not a lie about what kind of script the page wrote.
Acceptable narrow band-aid: keep a size heuristic only for scripts that are already async-eligible, or behind a flag for benchmarking. Never for defer or ordered scripts.
Context
Found during the x.com/nasa boot investigation on 2026-06-10 (branch feat/js-stack-trampoline). Sibling issues: "Interactive page load fires DOMContentLoaded before <script defer> runs" and "document.readyState is always complete".
External scripts over 200KB that the parser inserted with
defer(or with no attribute) are silently reclassified asasync. Defer scripts must run in document order beforeDOMContentLoaded. Async scripts run whenever. This reclassification is itself a band-aid for slow JS execution, and it breaks the ordering contract sites rely on.What happens
src/Starling.Engine/ScriptFetcher.cs:233-239promotes largeNone/Deferexternal scripts toAsync(theDeferringLargeScriptlog at line 237 records it, but the page cannot see it). All three x.com bundles qualify:vendor.b6dfc27a.js(689KB),i18n/en.ee2bc0fa.js(665KB),main.565e4e9a.js(1.4MB).Document order is preserved today only by accident —
RunAsyncScriptsiteratesFetcher.Scriptsin document order (src/Starling.Engine/Engine.cs:1456-1463). Nothing guarantees that stays true.The bigger problem: even after the
DOMContentLoaded-ordering bug (sibling issue) is fixed, this promotion would still push the bundles into the async step, afterDOMContentLoaded— keeping x.com's "Something went wrong" watchdog firing.Fix
This mechanism is a band-aid. It exists so huge bundles do not block the render-critical script phase, because executing multi-megabyte bundles is slow in the Starling JS engine (minutes for x.com). The real root cause is JS execution speed, which is tracked separately (inline caches and hidden-class work exist on an unmerged branch).
Real fix: remove the promotion for parser-inserted
deferand ordered scripts and restore spec classification. Keep first paint fast the spec-legal way instead: paint first, then run the defer batch, then fireDOMContentLoaded(the siblingDOMContentLoadedissue describes that pipeline).DOMContentLoadedthen arrives later than Chromium's by the execution time. That is honest, order-correct degradation under a slow engine — not a lie about what kind of script the page wrote.Acceptable narrow band-aid: keep a size heuristic only for scripts that are already
async-eligible, or behind a flag for benchmarking. Never fordeferor ordered scripts.Context
Found during the x.com/nasa boot investigation on 2026-06-10 (branch
feat/js-stack-trampoline). Sibling issues: "Interactive page load fires DOMContentLoaded before<script defer>runs" and "document.readyState is always complete".