Architektur-Gaps: Spec-konformer Proxy, Unified Execution Layer, Virtual Frame Clock #49
Labels
No labels
bug
docs
feature
housekeeping
html-spec
performance
react-compat
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
glow-all/true-headless-browser#49
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problembeschreibung
Unser Engine hat 3 persistente Gaps, die nach isolierten Fixes (safeWindow Proxy in evaluate.ts, FastRAF-Shim) weiterhin als Spezifikationsverletzungen bestehen:
Gap 1 — Proxy-Layer Transparenz (tolerantProxy)
ECMAScript §9.4.1: Unsetzte Property Reads auf einem Objekt MÜSSEN
undefinedergeben. UnsertolerantProxygibt stattdessen eine Callback-Funktion (typeof === 'function') zurück. Das führt zu:typeof React === 'function'bevor die CDN lädt (H4 Gap)define/moduleinstanceof-Checks können fehlschlagenGap 2 — Event-Loop-Timing (RAF + React Scheduler)
requestAnimationFrameist alssetTimeout(cb, 16)implementiert, ohne Frame-Boundaries. React 18 benutzt intern:scheduleUpdateOnFiber→ensureRootIsScheduled→postMessage/setTimeoutOhne Frame-Boundaries und korrekte Microtask-Verarbeitung bricht die Scheduler-Kette.
Gap 3 — Pipeline State Diffusion (3 Execution Paths + transient Context)
Script-Host-Code wird über 3+ unterschiedliche Execution Paths ausgeführt:
ExecutionRealm.execute()— named params (Phase 2), Nutzung in ScriptLoaderevaluate()—new Function()mit injectGlobals + safeWindow ProxyJeder Path hat andere:
Framework-Globals (React, Vue, $) die auf einem Path gesetzt werden, sind auf einem anderen unsichtbar.
Architektur-Analyse
Aktuelle Architektur — Execution Paths
Root Causes
Keine einheitliche Execution-Abstraktion — Jeder Consumer (ScriptLoader, evaluate, DynamicScriptHandler) implementiert eigene Scope/Proxy-Logik. Neue Features müssen an N Stellen nachgezogen werden.
tolerantProxy ist eine Zweck-Entität mit zwei Rollen:
undefinedgemäß ECMAScript SpectolerantProxy(function)→ Totalausfall Rolle BFrame-Timing existiert nicht — Kein Event-Loop mit Microtask-Checkpoints, Raf-Queue, oder Frame-Boundaries. React's Scheduler ist auf dieses Timing angewiesen, kann es aber nicht finden.
Lösungsansätze
Option A (empfohlen): Unified Execution Layer + Spec-Compliant Proxy + Virtual Frame Clock
Kernidee: Führe eine zentrale Execution-Abstraktion ein, die ALLEN Script-Typen denselben transparenten Window-Zugriff bietet, und trenne tolerantProxy's zwei Rollen in separate Layer.
Teil 1: Unified Execution Layer (UEL)
Eine einzige Engine-Komponente, die jeden Script-Host-Code ausführt:
Wie eliminiert das die Probleme:
UEL.execute()mit unterschiedlichen Optionen aufevaluate()undScriptLoader.executeRaw()nutzen denselben MechanismuTeil 2: Spec-Compliant Proxy — Layer Trennung
Aktueller
tolerantProxy(eine Entität mit zwei Rollen):Separiert in 3 Layer:
Der Production Proxy wechselt von:
Zu:
KNOWN_MISSING_APISist ein Set von wirklich fehlenden Browser-APIs:Diese Liste ist explizit, kurz, und wartbar — im Gegensatz zum aktuellen catch-all.
Teil 3: Virtual Frame Clock (VFC)
Ein Event-Loop-Layer der Frame-Boundaries simuliert:
Wie React's Scheduler davon profitiert:
React 18 intern:
Wenn
postMessagefehlt (unser Engine hat keine MessageChannel/Port), fällt React aufrequestAnimationFramezurück. Unser aktuelles RAF feuert aber nursetTimeout(cb, 16)— das erzeugt keinen echten Frame-Cycle. React'sperformWorkOnRoot→ yield →requestHostCallbacklandet im Nichts.Lösung: VFC stellt bereit:
requestAnimationFrame(cb)→ RAF-QueuerequestIdleCallback(cb)→ Idle-QueuepostMessage/MessageChannel→ exists as-is (Bun/Happy DOM hat das nativ)Option B: Isolierte Fixes (Status Quo + Oberschicht)
Fix-Varianten pro Layer (aktueller Ansatz):
evalaute.tssafeWindow Proxy → H4 behoben ✅_refreshParams()tolerantProxy-Filter → teilweise behobenevaluate.tsdynamisches scope → V2 behoben ✅Problem: Diese Fixes sind symptomatisch, nicht fundamental. Jeder neue Consumer (Node-Emulation, WebWorker, ServiceWorker) muss dieselben Fixes kopieren. Der Scheduler bleibt ohne Frame-Boundaries.
Option C: Spezialisierten DOM-Engine-Switch
Komplette Ablösung von Happy DOM's internem Window durch unsere eigene Implementation. Dann hätten wir volle Kontrolle über das Event-Loop-Modell.
Problem: Happy DOM löst ~1400 Spezifikationen (DOM, HTML5, CSSOM, etc.). Ein Switch bedeutet Jahre Neubau, während 3 Gaps bestehen. Nicht verhältnismäßig.
Entscheidung: Option A (empfohlen)
KNOWN_MISSING_APISist explizit statt catch-all; UEL ist ein Entry Point statt NAkzeptanzkriterien
Phase A — SpecProxy + tolerantProxy Separation
typeof React === 'undefined'vor CDN-Load (alle Execution Paths)typeof Vue === 'undefined'vor CDN-Loadtypeof jQuery === 'undefined'vor CDN-Loadtypeof $ === 'undefined'vor CDN-Loadtypeof _ === 'undefined'vor CDN-Loadtypeof define === 'undefined'(verhindert AMD UMD detection)typeof module === 'undefined'(verhindert AMD UMD detection)typeof webkitURL === 'function'(echte Missing-Browser-API wird tolerant)typeof setTimeout === 'function'KNOWN_MISSING_APISinitial mit mindestens 20 fehlenden APIsPhase B — Unified Execution Layer
UEL.execute()ersetztrealm.execute()in ScriptLoaderUEL.execute()ersetztnew Function()in evaluate.tswithRealm: booleanOptionwrapParameter: booleanOptionPhase C — Virtual Frame Clock
requestAnimationFrame(loop)≤ 20ms für 5 framesBetroffene Dateien
src/tolerant-proxy.tsKNOWN_MISSING_APISSet.src/js/execution-realm.ts_refreshParams()→ nutzt SpecProxy (kein extra tolerantProxy-Filter nötig).src/interaction/evaluate.tsnew Function(). safeWindow Proxy entfällt (SpecProxy macht das).src/js/script-loader.tssrc/js/dynamic-scripts.tssrc/fakes/fast-raf.tssrc/pages/page.tstests/unit/root-cause-gaps.test.tstests/unit/unified-execution-layer.test.tstests/unit/virtual-frame-clock.test.tsDependencies
src/fakes/fast-raf.ts— bereits existiert FastRAF in /src/fakes/. Wird durch VFC ersetzt.tests/unit/root-cause-gaps.test.ts— bereits existiert, 69 Tests. Deckt H4+V2+G3 ab.https://unpkg.com/react@18/umd/react.production.min.js— React CDN für Integration-Tests (G3.2/G3.3)Querverweise
src/tolerant-proxy.ts— aktuelle Implementation die umgebaut wirdsrc/js/execution-realm.ts:144— bestehender tolerantProxy-Filter (entfällt nach Phase A)src/interaction/evaluate.ts:80-85— bestehender safeWindow-Hack (entfällt nach Phase A)src/fakes/fast-raf.ts— FastRAF (wird durch VFC ersetzt)tests/unit/root-cause-gaps.test.ts— bestehende 69 Gap-TestsTechnische Risiken
Happy DOM Kompatibilität — Wir patchen das Window-Proxy-System. Happy DOM's interne Implementation (connectedToDocument, script-evaluation hooks) setzt bestimmte Window-Eigenschaften voraus. Lösung: Test-Suite vorher/nachher vergleichen, alle 1427 Tests müssen passen.
Webpack Chunk Capture bricht — Wenn SpecProxy
undefinedfür unbekannte Properties zurückgibt, könnten Webpack-chunk-Callback-Funktionen (webpackChunk...) nicht registriert werden. Lösung: Layer C (dynamicStorage) fängt diese ab, da Webpack sie setzt bevor Sie gelesen werden.Performance durch zusätzliche Proxy-Layer — Drei Layer statt einem könnten Property-Reads verlangsamen. Lösung: Layer sind kurzgeschlossen — Layer A prüft zuerst dynamicStorage (O(1) Map), dann target (nativer Reflect). Layer B nur für KNOWN_MISSING_APIS. Keine signifikante Verschlechterung erwartet.
React 18 concurrent mode Integration — React's Scheduler hat mehrere Fallback-Pfade (postMessage, RAF, setTimeout). Wenn VFC
postMessagezulässt aber RAF Frame-Boundaries liefert, könnte React beides konsumieren und duplizierte Updates erzeugen. Lösung: VFC musspostMessageaus React's Sicht blockieren ODER MessageChannel nativ unterstützen.Performance-Impact
buildVarDeclsentfällt (80 var Deklarationen pro Script). Netto-Gewinn.Testplan
Phase A — SpecProxy Tests (15+)
typeof React === 'undefined'via realm.execute()typeof Vue === 'undefined'via realm.execute()typeof jQuery === 'undefined'via page.evaluate()typeof $ === 'undefined'via page.evaluate()typeof define === 'undefined'(AMD detection prevention)typeof module === 'undefined'(AMD detection prevention)typeof webkitURL === 'function'(echte Missing API)typeof setTimeout === 'function'(echte Browser-API)KNOWN_MISSING_APISenthält mindestens 20 Einträgewindow.React = 42 → typeof React === 'number'(Set über Schreiben)Phase B — UEL Tests (20+)
UEL.execute(inline code)→ Resultat korrektUEL.execute(code, {withRealm: true})→ shared state zwischen CallsUEL.execute(code, {withRealm: false})→ isolated statePhase C — VFC Tests (15+)
✅ Phase A abgeschlossen (2026-06-29)
SpecProxy 3-Layer Architecture implementiert:
undefinedfür unbekannte Properties (ECMAScript §9.4.1)KNOWN_MISSING_APIS(webkitURL, chrome.csi, etc.) → tolerantProxyResultate
typeof React === 'undefined'vor CDN (alle Pfade)typeof webkitURL === 'function'(Missing API tolerant)Offen für Phase B + C
PR: #50
✅ Phase C abgeschlossen (2026-06-29) — ALLE 3 GAPS GESCHLOSSEN
Virtual Frame Clock implementiert:
requestAnimationFrameaus Callback → nächster FramerequestIdleCallbackmittimeRemaining()Finales Ergebnis
typeof React === 'function'vor CDNTest-Ergebnisse
Merge-Bereit
PR #50 enthält alle 3 Phasen. Ready to merge →
master.✅ Alle 3 Phasen gemerged (
bc87130)PR #50 wurde auf
mastergemerged. Issue #49 ist geschlossen.Merge-Zusammenfassung
Remote master enthält zusätzlich (unabhängige Arbeit):
Test-Status: 0 Regression. G3, H4, V2 — alle geschlossen.
Fix-Commit
a7c3cbc— named-params + SpecProxy Kompatibilität:isKnownMissingAPI+createTolerantProxyin_refreshParams()PARAM_COMPATIBLE_MISSING(single-level Missing-APIs) als named paramsrequestAnimationFrame/cancelAnimationFrame/requestIdleCallbackinBROWSER_PARAMSAlle 60 SpecProxy-Tests pass ✅ —
typeof webkitURL === 'function'auch via realm.execute().