Sprint 4: 'instanceof' TypeError bei fehlenden Browser-Constructors (SVGElement, MathMLElement) #54

Closed
opened 2026-06-18 12:21:56 +00:00 by Artur · 1 comment
Owner

Problembeschreibung

Nach Sprint 3 (document is not defined gefixt) tritt ein neuer TypeError auf:

TypeError: Right hand side of instanceof is not an object

Stacktrace (Vue Runtime, vuejs.org):

e instanceof SVGElement   // ← SVGElement ist undefined!
e instanceof MathMLElement

Root Cause: Der Proxy-Window (strict-Mode) gibt undefined für nicht-existente Browser-APIs zurück (specProxyGet Layer D). Wenn Module-Code x instanceof SVGElement macht, ist SVGElementwindow.SVGElement → Proxy-get → undefinedinstanceof undefinedTypeError.

Betroffen sind ALLE instanceof-Prüfungen gegen Browser-Klassen die Happy DOM nicht implementiert:

  • SVGElement (Vue renderer: vt()e instanceof SVGElement)
  • MathMLElement (Vue renderer: e instanceof MathMLElement)
  • Potentiell hunderte weitere: ShadowRoot, HTMLDialogElement, TextTrack, MediaStream, AudioContext, BroadcastChannel, ServiceWorker, etc.

Analyse

Proxy-Fallthrough

In tolerant-proxy.ts:strictProxyGet():

Layer C: dynamicStorage → checked ✓
Layer A: target (allowedGlobals) → checked ✓
Layer D: KNOWN_MISSING_APIS → undefined ✗
Fallback: → undefined ❌

Jeder unbekannte Key gibt undefined zurück. instanceof undefined ist ein TypeError.

KNOWN_MISSING_APIS ist nicht genug

KNOWN_MISSING_APIS ist eine statische Liste (aktuell ~30 Einträge). Es gibt hunderte nicht-implementierte Browser-APIs. Jede Website könnte jede davon via instanceof nutzen.

Lösungsansätze

Option A: NullBrowserClass — Smart Fallback für Constructor-Namen

Die abstrakteste und zukunftssicherste Lösung:

class NullBrowserClass {
  static [Symbol.hasInstance]() { return false; }
}
// im Proxy-GET:
if (/^[A-Z]/.test(key)) return NullBrowserClass;

Damit: x instanceof SVGElementfalse statt TypeError.

  • typeof NullBrowserClass"function"
  • NullBrowserClass.prototype → existiert
  • new NullBrowserClass() → Nat-Error (akzeptabel für Constructor-Call) ⚠️
  • Symbol.hasInstance → immer false

Option B: catch instanceof im ExecutionRealm

instanceof im Module-Code durch try/catch ersetzen — sehr invasiv (Code-Rewrite), keine Option.

Option C: Alle bekannten Constructor-Namen in KNOWN_MISSING_APIS

Vollständige Liste aller nicht-implementierten Browser-APIs anlegen:

  • DOM APIs: SVGElement, MathMLElement, HTMLDialogElement, HTMLDetailsElement, ShadowRoot, HTMLTemplateElement.content...
  • Media APIs: MediaStream, AudioContext, MediaRecorder...
  • Netzwerk: BroadcastChannel, WebSocketStream...
  • Storage: IDBFactory, IDBDatabase, IDBObjectStore...
  • Performance: PerformanceObserver, PerformanceEntry...
  • ...

Aufwändig (100+ Einträge) und nicht zukunftssicher.

Option D: Proxy gibt null statt undefined

instanceof null wirft auch TypeError. Keine Lösung.

Akzeptanzkriterien

  • x instanceof SVGElement wirft keinen TypeError mehr (return false)
  • x instanceof MathMLElement wirft keinen TypeError mehr (return false)
  • Alle anderen instanceof-Prüfungen gegen unbekannte Globals sind sicher
  • typeof SVGElement === "function" (anders als undefined)
  • typeof MathMLElement === "function"
  • [ ] Keine Regression bei VP_HASH_MAP`-Sync
  • vuejs.org lädt ohne instanceof-TypeError (auch wenn andere Fehler bleiben)
  • Bestehende Tests: tests/issue-51-vphashmap-sync.test.ts

Betroffene Dateien

Datei Änderung
src/tolerant-proxy.ts strictProxyGet/specProxyGet: Fallback für Constructor-Namen
src/runtime-isolation.ts KNOWN_MISSING_APIS erweitern oder NullBrowserClass importieren

Cross-Ref

  • #51: VP_HASH_MAP Sync (gelöst)
  • #52: globalThis Sync in Sub-Modules (gelöst)
  • #53: document in Sub-Modules, Prologue Single-Anchor (gelöst)

Details zum Fix (Option A — empfohlen)

Die Idee: Im Proxy-get-Trap, WENN der Key mit Großbuchstaben beginnt (Constructor-Pattern) UND nicht in dynamicStorage oder target ist, NICHT undefined zurückgeben, sondern eine NullBrowserClass.

// tolerant-proxy.ts
class NullBrowserClass {
  /** Name for debugging */
  readonly __name: string;
  constructor(name: string) { this.__name = name; }
  static [Symbol.hasInstance](): boolean { return false; }
}

Eingebaut in strictProxyGet():

// Layer E: Unknown upper-case → Constructor-Fallback
if (/^[A-Z]/.test(key)) {
  return new NullBrowserClass(key);
}

Damit:

  • typeof window.SVGElement"object" (nicht ideal, besser wäre "function")
  • SVGElement[Symbol.hasInstance]false
  • x instanceof SVGElementfalse

Alternativ: stattdessen function zurückgeben:

const fn = () => { throw new Error(`${key} not implemented`); };
fn[Symbol.hasInstance] = () => false;
return fn;
  • typeof window.SVGElement"function"
  • x instanceof SVGElementfalse
  • Aufruf: new SVGElement() wirft Error statt TypeError

Test Case

// tests/issue-53-nullbrowserclass.test.ts
const { Page } = require("../src/pages/page");
const page = new Page({ mode: "strict" });
await page.loadHTML(`<!DOCTYPE html><html><body>
<script>
  console.log(typeof SVGElement, typeof MathMLElement, typeof HTMLDialogElement);
  console.log(document.createElement("div") instanceof SVGElement);
</script>
</body></html>`, "http://localhost/test");
## Problembeschreibung Nach Sprint 3 (`document is not defined` gefixt) tritt ein neuer TypeError auf: ``` TypeError: Right hand side of instanceof is not an object ``` Stacktrace (Vue Runtime, vuejs.org): ``` e instanceof SVGElement // ← SVGElement ist undefined! e instanceof MathMLElement ``` **Root Cause:** Der Proxy-Window (`strict`-Mode) gibt `undefined` für nicht-existente Browser-APIs zurück (`specProxyGet` Layer D). Wenn Module-Code `x instanceof SVGElement` macht, ist `SVGElement` → `window.SVGElement` → Proxy-get → `undefined` → `instanceof undefined` → **TypeError**. Betroffen sind ALLE `instanceof`-Prüfungen gegen Browser-Klassen die Happy DOM nicht implementiert: - `SVGElement` (Vue renderer: `vt()` → `e instanceof SVGElement`) - `MathMLElement` (Vue renderer: `e instanceof MathMLElement`) - Potentiell hunderte weitere: `ShadowRoot`, `HTMLDialogElement`, `TextTrack`, `MediaStream`, `AudioContext`, `BroadcastChannel`, `ServiceWorker`, etc. ## Analyse ### Proxy-Fallthrough In `tolerant-proxy.ts:strictProxyGet()`: ``` Layer C: dynamicStorage → checked ✓ Layer A: target (allowedGlobals) → checked ✓ Layer D: KNOWN_MISSING_APIS → undefined ✗ Fallback: → undefined ❌ ``` Jeder unbekannte Key gibt `undefined` zurück. `instanceof undefined` ist ein TypeError. ### `KNOWN_MISSING_APIS` ist nicht genug `KNOWN_MISSING_APIS` ist eine statische Liste (aktuell ~30 Einträge). Es gibt hunderte nicht-implementierte Browser-APIs. Jede Website könnte jede davon via `instanceof` nutzen. ### Lösungsansätze **Option A: NullBrowserClass — Smart Fallback für Constructor-Namen** Die abstrakteste und zukunftssicherste Lösung: ```javascript class NullBrowserClass { static [Symbol.hasInstance]() { return false; } } // im Proxy-GET: if (/^[A-Z]/.test(key)) return NullBrowserClass; ``` Damit: `x instanceof SVGElement` → `false` statt TypeError. - `typeof NullBrowserClass` → `"function"` ✅ - `NullBrowserClass.prototype` → existiert ✅ - `new NullBrowserClass()` → Nat-Error (akzeptabel für Constructor-Call) ⚠️ - `Symbol.hasInstance` → immer `false` ✅ **Option B: catch `instanceof` im ExecutionRealm** `instanceof` im Module-Code durch try/catch ersetzen — sehr invasiv (Code-Rewrite), keine Option. **Option C: Alle bekannten Constructor-Namen in `KNOWN_MISSING_APIS`** Vollständige Liste aller nicht-implementierten Browser-APIs anlegen: - DOM APIs: `SVGElement`, `MathMLElement`, `HTMLDialogElement`, `HTMLDetailsElement`, `ShadowRoot`, `HTMLTemplateElement.content`... - Media APIs: `MediaStream`, `AudioContext`, `MediaRecorder`... - Netzwerk: `BroadcastChannel`, `WebSocketStream`... - Storage: `IDBFactory`, `IDBDatabase`, `IDBObjectStore`... - Performance: `PerformanceObserver`, `PerformanceEntry`... - ... Aufwändig (100+ Einträge) und nicht zukunftssicher. **Option D: Proxy gibt `null` statt `undefined`** `instanceof null` wirft auch TypeError. Keine Lösung. ## Akzeptanzkriterien - [ ] `x instanceof SVGElement` wirft keinen TypeError mehr (return `false`) - [ ] `x instanceof MathMLElement` wirft keinen TypeError mehr (return `false`) - [ ] Alle anderen `instanceof`-Prüfungen gegen unbekannte Globals sind sicher - [ ] `typeof SVGElement === "function"` (anders als `undefined`) - [ ] `typeof MathMLElement === "function"` - [ `] Keine Regression bei `__VP_HASH_MAP__`-Sync - [ ] vuejs.org lädt ohne `instanceof`-TypeError (auch wenn andere Fehler bleiben) - [ ] Bestehende Tests: `tests/issue-51-vphashmap-sync.test.ts` ✅ ## Betroffene Dateien | Datei | Änderung | |-------|----------| | `src/tolerant-proxy.ts` | `strictProxyGet`/`specProxyGet`: Fallback für Constructor-Namen | | `src/runtime-isolation.ts` | `KNOWN_MISSING_APIS` erweitern oder `NullBrowserClass` importieren | ## Cross-Ref - #51: __VP_HASH_MAP__ Sync (gelöst) - #52: globalThis Sync in Sub-Modules (gelöst) - #53: document in Sub-Modules, Prologue Single-Anchor (gelöst) ## Details zum Fix (Option A — empfohlen) Die Idee: Im Proxy-`get`-Trap, WENN der Key mit Großbuchstaben beginnt (Constructor-Pattern) UND nicht in `dynamicStorage` oder `target` ist, NICHT `undefined` zurückgeben, sondern eine `NullBrowserClass`. ```typescript // tolerant-proxy.ts class NullBrowserClass { /** Name for debugging */ readonly __name: string; constructor(name: string) { this.__name = name; } static [Symbol.hasInstance](): boolean { return false; } } ``` Eingebaut in `strictProxyGet()`: ```typescript // Layer E: Unknown upper-case → Constructor-Fallback if (/^[A-Z]/.test(key)) { return new NullBrowserClass(key); } ``` Damit: - `typeof window.SVGElement` → `"object"` (nicht ideal, besser wäre `"function"`) - `SVGElement[Symbol.hasInstance]` → `false` ✅ - `x instanceof SVGElement` → `false` ✅ Alternativ: stattdessen `function` zurückgeben: ```typescript const fn = () => { throw new Error(`${key} not implemented`); }; fn[Symbol.hasInstance] = () => false; return fn; ``` - `typeof window.SVGElement` → `"function"` ✅ - `x instanceof SVGElement` → `false` ✅ - Aufruf: `new SVGElement()` wirft Error statt TypeError ✅ ## Test Case ```typescript // tests/issue-53-nullbrowserclass.test.ts const { Page } = require("../src/pages/page"); const page = new Page({ mode: "strict" }); await page.loadHTML(`<!DOCTYPE html><html><body> <script> console.log(typeof SVGElement, typeof MathMLElement, typeof HTMLDialogElement); console.log(document.createElement("div") instanceof SVGElement); </script> </body></html>`, "http://localhost/test"); ```
Artur closed this issue 2026-06-18 12:41:10 +00:00
Author
Owner

Implementiert (9af39a1)

Layer E: NullBrowserClass in tolerant-proxy.ts

+ if (/^[A-Z]/.test(key)) {
+   return createNullBrowserClass(key);
+ }

9/9 Tests passen:

  • typeof SVGElement === "function"
  • div instanceof SVGElement === false
  • new SVGElement() throws TypeError (diagnostische Meldung)
  • typeof MathMLElement === "function"
  • div instanceof MathMLElement === false
  • typeof window === "object" (bestehende APIs)
  • react === undefined (Framework-Globals)
  • typeof webkitURL === "function" (KNOWN_MISSING_APIS)

Nächster Fehler bei vuejs.org:
IntersectionObserver is not implemented by this browser
→ Saubere, implementierbare API-Lücke → Sprint 5.

Test: bun run tests/null-browser-class.test.ts

## Implementiert ✅ (9af39a1) **Layer E: NullBrowserClass** in `tolerant-proxy.ts` ```diff + if (/^[A-Z]/.test(key)) { + return createNullBrowserClass(key); + } ``` **9/9 Tests passen:** - ✅ `typeof SVGElement === "function"` - ✅ `div instanceof SVGElement === false` - ✅ `new SVGElement() throws TypeError` (diagnostische Meldung) - ✅ `typeof MathMLElement === "function"` - ✅ `div instanceof MathMLElement === false` - ✅ `typeof window === "object"` (bestehende APIs) - ✅ `react === undefined` (Framework-Globals) - ✅ `typeof webkitURL === "function"` (KNOWN_MISSING_APIS) **Nächster Fehler bei vuejs.org:** `IntersectionObserver is not implemented by this browser` → Saubere, implementierbare API-Lücke → Sprint 5. **Test:** `bun run tests/null-browser-class.test.ts`
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
glow-all/true-headless-browser#54
No description provided.