#108: Layout Engine — Element-Klassifikation + Default-Geometrie (Non-Visual) #108

Closed
opened 2026-06-19 15:48:59 +00:00 by Artur · 1 comment
Owner

Problembeschreibung

offsetWidth, offsetHeight, clientWidth, clientHeight, getBoundingClientRect(), scrollTop, scrollLeft geben aktuell fake/default-Werte zuruck, die nicht zwischen Elementtypen unterscheiden.

Viele Websites und Frameworks lesen diese Werte:

  • Lazy-Loading: el.getBoundingClientRect().top < window.innerHeight fur Image-Intersection
  • Infinite Scroll: container.scrollTop + container.clientHeight >= container.scrollHeight
  • Dropdowns/Popups: button.getBoundingClientRect() zum Positionieren
  • Resize-Listener: element.clientWidth fur responsive Layouts
  • Framework-Checks: React checkt el.clientHeight > 0 fur Visibility

NON-VISUAL-OPTIMIERUNG: Wir brauchen KEINEN echten Layout-Engine (kein Reflow, kein Box-Model). Stattdessen liefern wir DEFAULT-Werte die "gut genug" fur JS-Checks sind, ohne jemals Pixel-gepixeltes Layout zu berechnen.

Architektur-Analyse

Aktueller Stand

// src/dom/node.ts (Fake-Werte via getBoundingClientRect-Shim)
getBoundingClientRect() {
  if (this.__fakeGetBoundingClientRect) return { x: 0, y: 0, width: 0, height: 0, top: 0, right: 0, bottom: 0, left: 0 };
  // ... real implementation
}

// Fake offsetHeight bei body/html:
if (tag === "BODY") { offsetHeight = viewportHeight; }
if (tag === "HTML") { offsetHeight = viewportHeight; }

Ziel-Architektur — Non-Visual-Layout

flowchart TB
    subgraph "Non-Visual Layout Engine"
        A["Element.getBoundingClientRect()"] --> B{Has explicit override?}
        B -->|"Body/HTML"| C["Viewport-Based Rect"]
        B -->|"DocumentFragment"| D["{0,0,0,0}"]
        B -->|"display:none"| E["{0,0,0,0}"]
        B -->|"Default"| F["{0,0,1,1}"]
    end
    
    subgraph "Dimension Properties"
        G["offsetWidth/offsetHeight"]
        H["clientWidth/clientHeight"]
        I["scrollWidth/scrollHeight"]
        J["scrollTop/scrollLeft"]
    end
    
    subgraph "NO Implementation"
        K["Box-Model (margin/border/padding)"]
        L["Font Glyph Metrics"]
        M["Text-Wrapping"]
        N["Float/Clear"]
        O["Position: absolute/fixed"]
        P["Flexbox/Grid"]
        Q["Multi-Column"]
    end
    
    F -->|"1px width, 1px height"| R["JS check rect.width > 0 ✓"]
    C -->|"Viewport size"| S["documentElement/clientHeight = innerHeight ✓"]

Non-Visual Optimierungen (Das Protokoll)

Check-Typ Framework-Code Unser Wert
Visibility el.offsetParent !== null null (nie versteckt)
Visibility el.clientHeight > 0 1 (nie null)
Sizing el.getBoundingClientRect().width 1 (nie 0)
Scroll el.scrollTop > 0 0
Max-Scroll el.scrollHeight > el.clientHeight false
Overflow el.scrollWidth > el.clientWidth false
Intersection rect.top < innerHeight Jeder Rect ist "sichtbar"
Position el.offsetTop 0
Edge el.getClientRects() [DOMRect] (nie leer)

Kernprinzip: Jede Zahl die JS lesen konnte, ist > 0 oder 0 — nie undefined/null. Jeder Rect, der abgefragt wird, ist im Viewport sichtbar.

Root Causes

  1. Keine element-spezifischen Defaults: getBoundingClientRect() gibt {0,0,0,0} fur ALLES — inklusive <body> und <html> die per Viewport simuliert werden mussen
  2. Kein display:none Check: display: none Elemente mussen {0,0,0,0} zuruckgeben — aktuell immer {0,0,0,0}
  3. Kein scrollBehavior: scrollTop wird nie gelesen (fur Infinite Scroll Checks)
  4. Kein offsetParent: Manche Frameworks prufen el.offsetParent !== null fur Sichtbarkeit

Losungsansatze

Option A (empfohlen): Non-Visual-Layout per Default-Klassifikation

Kernidee: Ein Klassifikationssystem das jedem Element einen "Layout-Typ" zuweist (Block, Inline, Replaced, Document). Basierend auf Typ werden Default-Großen zuruckgegeben, ohne jemals Geometrie zu berechnen.

Teil 1: Element-Klassifikation

// src/css/element-layout.ts (NEU)
type ElementLayoutClass =
  | "document"   // html, body
  | "block"      // div, p, section, ...
  | "inline"     // span, a, ...
  | "replaced"   // img, canvas, video, ...
  | "void"       // br, hr, ...
  | "table"      // table, tr, td, ...
  | "none";      // display: none

function classifyElement(el: Element): ElementLayoutClass {
  const tag = el._tagName.toLowerCase();
  if (tag === "html" || tag === "body") return "document";
  if (tag === "img" || tag === "canvas" || tag === "video" || tag === "audio" || tag === "input") return "replaced";
  if (tag === "table" || tag === "tr" || tag === "td" || tag === "th" || tag === "thead" || tag === "tbody") return "table";
  // display: none detection
  try { if (getComputedStyle(el).display === "none") return "none"; } catch {}
  return "block";
}

Teil 2: Layout-Default-Werte pro Klasse

// Layout-Konstanten — einmal definiert, nie recalculated
const LAYOUT_DEFAULTS = {
  document: { width: 1366, height: 768, offsetWidth: 1366, offsetHeight: 768, scrollHeight: 768 },
  block:    { width: 300,  height: 20,  offsetWidth: 300,  offsetHeight: 20,  scrollHeight: 20 },
  inline:   { width: 50,   height: 16,  offsetWidth: 50,   offsetHeight: 16,  scrollHeight: 16 },
  replaced: { width: 150,  height: 150, offsetWidth: 150,  offsetHeight: 150, scrollHeight: 150 },
  void:     { width: 0,    height: 0,   offsetWidth: 0,    offsetHeight: 0,   scrollHeight: 0 },
  table:    { width: 300,  height: 100, offsetWidth: 300,  offsetHeight: 100, scrollHeight: 100 },
  none:     { width: 0,    height: 0,   offsetWidth: 0,    offsetHeight: 0,   scrollHeight: 0 },
};

Teil 3: Getters auf Element.prototype

// src/dom/node.ts — Element Layout Getter
class Element extends Node {
  get offsetWidth(): number {
    return this._isDisplayNone() ? 0 : this._getLayoutDefault().offsetWidth;
  }
  get offsetHeight(): number {
    return this._isDisplayNone() ? 0 : this._getLayoutDefault().offsetHeight;
  }
  getBoundingClientRect(): DOMRect {
    if (this._isDisplayNone()) return new DOMRect(0, 0, 0, 0);
    const cls = this._getLayoutClass();
    if (cls === "document") return new DOMRect(0, 0, 1366, 768);
    // Position elements at reasonable defaults for z-ordering checks
    return new DOMRect(10, 10, LAYOUT_DEFAULTS[cls].width, LAYOUT_DEFAULTS[cls].height);
  }
}

Vorteile:

  • Element-Typ-Unterscheidung: body(1366x768) != span(50x16) != img(150x150)
  • display: none → 0,0,0,0 — korrektes Spec-Verhalten
  • Kein Layout-Reflow, kein Box-Model, keine Font-Metrics
  • Framework-Checks bestehen: clientHeight > 0 → true fur sichtbare Elemente
  • getBoundingClientRect ist immer im Viewport → IntersectionObserver-Simulation ok
  • <html>/<body> haben Viewport-Große wie im echten Browser

Nachteile:

  • Alle Block-Elemente haben selbe Große (300x20) — keine Unterscheidung nach Content
  • scrollHeight == clientHeight → Infinite-Scroll erkennt nie Scroll-Overflow
  • offsetLeft == 10 (hartcodiert) — keine echte XY-Positionierung

Option B: Text-Breite-Schatzung

Word-Count-basierte Großen-Schatzung: offsetWidth = wordCount * 8 + padding (approx).

Problem: Schatzung ist immer falsch (Font-Metrics fehlen). 50% Schatzfehler = gleiche Qualitat wie Option A, aber 100x aufwandiger. Die Praktikabilitat fur Headless ist identisch zu A.

Option C: Vollstandiger Layout-Engine

CSS-Box-Model mit Block-Formatting-Context, Inline-Formatting, Positioning.

Problem: Eine echte Layout-Engine ware ~5000 LOC und musste alle CSS-Features abdecken (Flexbox, Grid, Float, Position, Table). Fur Headless-Scraping ist dies Overkill — Frameworks checken nur > 0 oder !== null, sie brauchen keine Pixel-genauen Werte.

Entscheidung: Option A

  1. Framework-Checks sind binare Entscheidungen: > 0, !== null, in viewport — nie Pixel-genaue Werte. Option A liefert korrekte binare Ergebnisse.
  2. Element-Typ-Unterscheidung ist kritisch: <img> muss offsetWidth > 0 haben, <body> muss Viewport-Position haben. Option A klassifiziert korrekt.
  3. display: none Handhabung: Option A erkennt display: none via getComputedStyle und gibt {0,0,0,0} — korrektes Spec-Verhalten.

Akzeptanzkriterien

  • document.documentElement.offsetHeight == Viewport-Hohe (768)
  • document.body.offsetHeight == Viewport-Hohe
  • el.offsetWidth > 0 fur sichtbare Block-Elemente
  • el.offsetWidth == 0 bei display: none
  • el.getBoundingClientRect().width > 0 fur sichtbare Elemente
  • el.getBoundingClientRect().width == 0 bei display: none
  • <img> hat offsetWidth = 150 (Replaced-Element Default)
  • <span> hat offsetWidth = 50 (Inline-Element Default)
  • getClientRects() gibt nicht-leeres Array zuruck
  • el.scrollTop == 0, el.scrollLeft == 0 (nie gescrollt)
  • el.scrollWidth == el.clientWidth (nie overflow)
  • el.clientHeight == el.offsetHeight (kein border)
  • el.offsetParent gibt null zuruck (nie position:absolute/relative/fixed)
  • Viewport-Elemente (html/body) haben Position (0,0) im Rect
  • Keine Regression bei bestehenden Tests

Betroffene Dateien

Datei Anderung Status
src/css/element-layout.ts Layout-Klassifikation + Defaults NEU
src/dom/node.ts Element Layout-Getter (offset*, getBoundingClientRect, client*) Andern
src/dom/window.ts viewportWidth/Height an Layout-System ubergeben Andern
tests/unit/dom-layout.test.ts 20+ Tests NEU

Dependencies

  • Issue #107 — CSSOM/getComputedStyle (benotigt fur display: none Detection)
  • Issue #104 — Happy DOM Replacement (gelost) — Element-Klassifikation erfordert eigene Elemente

Technische Risiken

  1. display: none Erkennung: Muss via getComputedStyle funktionieren — zirkulare Abhangigkeit zwischen Layout (benotigt display:none) und CSSOM (benotigt Layout nicht). Losung: display: none wird direkt aus el.style.display gelesen, nicht via getComputedStyle.
  2. offsetParent-Semantik: offsetParent ist null fur position: fixed, display: none, <body>, <html>. Unser null-Ruckgabewert ist korrekt fur die meisten Framework-Checks.
  3. getClientRects() fur inline-Elemente: Sollte ein Array von DOMRects pro Textzeile zuruckgeben. Da wir kein Text-Wrapping haben, geben wir [el.getBoundingClientRect()] zuruck — korrekt fur die meisten Anwendungsfalle.

Performance-Impact

  • offsetWidth/Height: ~0.001ms (Klassifikation per String-Compare)
  • getBoundingClientRect: ~0.002ms (Klassifikation + Rect-Erstellung)
  • getClientRects: ~0.003ms (Rect-Erstellung + Array)
  • Erwartet: ~0.005ms zusatzlich pro Layout-Abfrage
  • Optimierungspotential: Layout-Klassifikation per _layoutClass Cache auf Element, invalidiert bei style.display-Anderung

Testplan

Unit-Tests (20+)

  1. body.offsetHeight == viewportHeight
  2. html.offsetHeight == viewportHeight
  3. div.offsetWidth > 0
  4. div.offsetWidth == 0 bei style.display="none"
  5. getBoundingClientRect().width > 0 fur sichtbares div
  6. getBoundingClientRect().width == 0 bei display:none
  7. img.offsetWidth == 150
  8. span.offsetWidth == 50
  9. clientWidth == offsetWidth (kein border)
  10. scrollWidth == clientWidth (kein overflow)
  11. scrollTop == 0
  12. getClientRects().length > 0
  13. getClientRects()[0] entspricht getBoundingClientRect()
  14. offsetParent == null
  15. br.offsetWidth == 0 (void element)
  16. table.offsetWidth == 300
  17. getBoundingClientRect().top == 10 (hartcodiert)
  18. Fragment.children[0].offsetWidth > 0
  19. Fragment mit display:none Kind: offsetWidth == 0
  20. body.getBoundingClientRect() viewport positions

Integration-Tests (3+)

  1. createIsolatedContext mit Layout-Defaults
  2. Parser baut Elemente mit korrekten Layout-Werten
  3. Framework-artiger Check: el.offsetHeight > 0 fur created Elemente
## Problembeschreibung `offsetWidth`, `offsetHeight`, `clientWidth`, `clientHeight`, `getBoundingClientRect()`, `scrollTop`, `scrollLeft` geben aktuell fake/default-Werte zuruck, die nicht zwischen Elementtypen unterscheiden. Viele Websites und Frameworks lesen diese Werte: - **Lazy-Loading**: `el.getBoundingClientRect().top < window.innerHeight` fur Image-Intersection - **Infinite Scroll**: `container.scrollTop + container.clientHeight >= container.scrollHeight` - **Dropdowns/Popups**: `button.getBoundingClientRect()` zum Positionieren - **Resize-Listener**: `element.clientWidth` fur responsive Layouts - **Framework-Checks**: React checkt `el.clientHeight > 0` fur Visibility **NON-VISUAL-OPTIMIERUNG: Wir brauchen KEINEN echten Layout-Engine (kein Reflow, kein Box-Model). Stattdessen liefern wir DEFAULT-Werte die "gut genug" fur JS-Checks sind, ohne jemals Pixel-gepixeltes Layout zu berechnen.** ## Architektur-Analyse ### Aktueller Stand ```ts // src/dom/node.ts (Fake-Werte via getBoundingClientRect-Shim) getBoundingClientRect() { if (this.__fakeGetBoundingClientRect) return { x: 0, y: 0, width: 0, height: 0, top: 0, right: 0, bottom: 0, left: 0 }; // ... real implementation } // Fake offsetHeight bei body/html: if (tag === "BODY") { offsetHeight = viewportHeight; } if (tag === "HTML") { offsetHeight = viewportHeight; } ``` ### Ziel-Architektur — Non-Visual-Layout ```mermaid flowchart TB subgraph "Non-Visual Layout Engine" A["Element.getBoundingClientRect()"] --> B{Has explicit override?} B -->|"Body/HTML"| C["Viewport-Based Rect"] B -->|"DocumentFragment"| D["{0,0,0,0}"] B -->|"display:none"| E["{0,0,0,0}"] B -->|"Default"| F["{0,0,1,1}"] end subgraph "Dimension Properties" G["offsetWidth/offsetHeight"] H["clientWidth/clientHeight"] I["scrollWidth/scrollHeight"] J["scrollTop/scrollLeft"] end subgraph "NO Implementation" K["Box-Model (margin/border/padding)"] L["Font Glyph Metrics"] M["Text-Wrapping"] N["Float/Clear"] O["Position: absolute/fixed"] P["Flexbox/Grid"] Q["Multi-Column"] end F -->|"1px width, 1px height"| R["JS check rect.width > 0 ✓"] C -->|"Viewport size"| S["documentElement/clientHeight = innerHeight ✓"] ``` ### Non-Visual Optimierungen (Das Protokoll) | Check-Typ | Framework-Code | Unser Wert | |-----------|---------------|------------| | Visibility | `el.offsetParent !== null` | `null` (nie versteckt) | | Visibility | `el.clientHeight > 0` | `1` (nie null) | | Sizing | `el.getBoundingClientRect().width` | `1` (nie 0) | | Scroll | `el.scrollTop > 0` | `0` | | Max-Scroll | `el.scrollHeight > el.clientHeight` | `false` | | Overflow | `el.scrollWidth > el.clientWidth` | `false` | | Intersection | `rect.top < innerHeight` | Jeder Rect ist "sichtbar" | | Position | `el.offsetTop` | `0` | | Edge | `el.getClientRects()` | `[DOMRect]` (nie leer) | **Kernprinzip: Jede Zahl die JS lesen konnte, ist > 0 oder 0 — nie undefined/null. Jeder Rect, der abgefragt wird, ist im Viewport sichtbar.** ### Root Causes 1. **Keine element-spezifischen Defaults**: `getBoundingClientRect()` gibt `{0,0,0,0}` fur ALLES — inklusive `<body>` und `<html>` die per Viewport simuliert werden mussen 2. **Kein display:none Check**: `display: none` Elemente mussen `{0,0,0,0}` zuruckgeben — aktuell immer `{0,0,0,0}` 3. **Kein scrollBehavior**: `scrollTop` wird nie gelesen (fur Infinite Scroll Checks) 4. **Kein offsetParent**: Manche Frameworks prufen `el.offsetParent !== null` fur Sichtbarkeit ## Losungsansatze ### Option A (empfohlen): Non-Visual-Layout per Default-Klassifikation **Kernidee:** Ein Klassifikationssystem das jedem Element einen "Layout-Typ" zuweist (Block, Inline, Replaced, Document). Basierend auf Typ werden Default-Großen zuruckgegeben, ohne jemals Geometrie zu berechnen. **Teil 1: Element-Klassifikation** ```ts // src/css/element-layout.ts (NEU) type ElementLayoutClass = | "document" // html, body | "block" // div, p, section, ... | "inline" // span, a, ... | "replaced" // img, canvas, video, ... | "void" // br, hr, ... | "table" // table, tr, td, ... | "none"; // display: none function classifyElement(el: Element): ElementLayoutClass { const tag = el._tagName.toLowerCase(); if (tag === "html" || tag === "body") return "document"; if (tag === "img" || tag === "canvas" || tag === "video" || tag === "audio" || tag === "input") return "replaced"; if (tag === "table" || tag === "tr" || tag === "td" || tag === "th" || tag === "thead" || tag === "tbody") return "table"; // display: none detection try { if (getComputedStyle(el).display === "none") return "none"; } catch {} return "block"; } ``` **Teil 2: Layout-Default-Werte pro Klasse** ```ts // Layout-Konstanten — einmal definiert, nie recalculated const LAYOUT_DEFAULTS = { document: { width: 1366, height: 768, offsetWidth: 1366, offsetHeight: 768, scrollHeight: 768 }, block: { width: 300, height: 20, offsetWidth: 300, offsetHeight: 20, scrollHeight: 20 }, inline: { width: 50, height: 16, offsetWidth: 50, offsetHeight: 16, scrollHeight: 16 }, replaced: { width: 150, height: 150, offsetWidth: 150, offsetHeight: 150, scrollHeight: 150 }, void: { width: 0, height: 0, offsetWidth: 0, offsetHeight: 0, scrollHeight: 0 }, table: { width: 300, height: 100, offsetWidth: 300, offsetHeight: 100, scrollHeight: 100 }, none: { width: 0, height: 0, offsetWidth: 0, offsetHeight: 0, scrollHeight: 0 }, }; ``` **Teil 3: Getters auf Element.prototype** ```ts // src/dom/node.ts — Element Layout Getter class Element extends Node { get offsetWidth(): number { return this._isDisplayNone() ? 0 : this._getLayoutDefault().offsetWidth; } get offsetHeight(): number { return this._isDisplayNone() ? 0 : this._getLayoutDefault().offsetHeight; } getBoundingClientRect(): DOMRect { if (this._isDisplayNone()) return new DOMRect(0, 0, 0, 0); const cls = this._getLayoutClass(); if (cls === "document") return new DOMRect(0, 0, 1366, 768); // Position elements at reasonable defaults for z-ordering checks return new DOMRect(10, 10, LAYOUT_DEFAULTS[cls].width, LAYOUT_DEFAULTS[cls].height); } } ``` **Vorteile:** - Element-Typ-Unterscheidung: body(1366x768) != span(50x16) != img(150x150) - `display: none` → 0,0,0,0 — korrektes Spec-Verhalten - Kein Layout-Reflow, kein Box-Model, keine Font-Metrics - Framework-Checks bestehen: `clientHeight > 0` → true fur sichtbare Elemente - `getBoundingClientRect` ist immer im Viewport → IntersectionObserver-Simulation ok - `<html>`/`<body>` haben Viewport-Große wie im echten Browser **Nachteile:** - Alle Block-Elemente haben selbe Große (300x20) — keine Unterscheidung nach Content - `scrollHeight == clientHeight` → Infinite-Scroll erkennt nie Scroll-Overflow - `offsetLeft == 10` (hartcodiert) — keine echte XY-Positionierung ### Option B: Text-Breite-Schatzung Word-Count-basierte Großen-Schatzung: `offsetWidth = wordCount * 8 + padding` (approx). **Problem:** Schatzung ist immer falsch (Font-Metrics fehlen). 50% Schatzfehler = gleiche Qualitat wie Option A, aber 100x aufwandiger. Die Praktikabilitat fur Headless ist identisch zu A. ### Option C: Vollstandiger Layout-Engine CSS-Box-Model mit Block-Formatting-Context, Inline-Formatting, Positioning. **Problem:** Eine echte Layout-Engine ware ~5000 LOC und musste alle CSS-Features abdecken (Flexbox, Grid, Float, Position, Table). Fur Headless-Scraping ist dies Overkill — Frameworks checken nur `> 0` oder `!== null`, sie brauchen keine Pixel-genauen Werte. ## Entscheidung: Option A 1. **Framework-Checks sind binare Entscheidungen**: `> 0`, `!== null`, `in viewport` — nie Pixel-genaue Werte. Option A liefert korrekte binare Ergebnisse. 2. **Element-Typ-Unterscheidung ist kritisch**: `<img>` muss `offsetWidth > 0` haben, `<body>` muss Viewport-Position haben. Option A klassifiziert korrekt. 3. **`display: none` Handhabung**: Option A erkennt `display: none` via getComputedStyle und gibt `{0,0,0,0}` — korrektes Spec-Verhalten. ## Akzeptanzkriterien - [ ] `document.documentElement.offsetHeight` == Viewport-Hohe (768) - [ ] `document.body.offsetHeight` == Viewport-Hohe - [ ] `el.offsetWidth` > 0 fur sichtbare Block-Elemente - [ ] `el.offsetWidth` == 0 bei `display: none` - [ ] `el.getBoundingClientRect().width` > 0 fur sichtbare Elemente - [ ] `el.getBoundingClientRect().width` == 0 bei `display: none` - [ ] `<img>` hat offsetWidth = 150 (Replaced-Element Default) - [ ] `<span>` hat offsetWidth = 50 (Inline-Element Default) - [ ] `getClientRects()` gibt nicht-leeres Array zuruck - [ ] `el.scrollTop` == 0, `el.scrollLeft` == 0 (nie gescrollt) - [ ] `el.scrollWidth == el.clientWidth` (nie overflow) - [ ] `el.clientHeight == el.offsetHeight` (kein border) - [ ] `el.offsetParent` gibt `null` zuruck (nie position:absolute/relative/fixed) - [ ] Viewport-Elemente (html/body) haben Position (0,0) im Rect - [ ] Keine Regression bei bestehenden Tests ## Betroffene Dateien | Datei | Anderung | Status | |-------|----------|--------| | `src/css/element-layout.ts` | Layout-Klassifikation + Defaults | **NEU** | | `src/dom/node.ts` | Element Layout-Getter (offset*, getBoundingClientRect, client*) | Andern | | `src/dom/window.ts` | viewportWidth/Height an Layout-System ubergeben | Andern | | `tests/unit/dom-layout.test.ts` | 20+ Tests | **NEU** | ## Dependencies - Issue #107 — CSSOM/getComputedStyle (benotigt fur `display: none` Detection) - Issue #104 — Happy DOM Replacement (gelost) — Element-Klassifikation erfordert eigene Elemente ## Technische Risiken 1. **`display: none` Erkennung**: Muss via getComputedStyle funktionieren — zirkulare Abhangigkeit zwischen Layout (benotigt display:none) und CSSOM (benotigt Layout nicht). Losung: `display: none` wird direkt aus `el.style.display` gelesen, nicht via getComputedStyle. 2. **`offsetParent`-Semantik**: `offsetParent` ist `null` fur `position: fixed`, `display: none`, `<body>`, `<html>`. Unser `null`-Ruckgabewert ist korrekt fur die meisten Framework-Checks. 3. **`getClientRects()` fur inline-Elemente**: Sollte ein Array von DOMRects pro Textzeile zuruckgeben. Da wir kein Text-Wrapping haben, geben wir `[el.getBoundingClientRect()]` zuruck — korrekt fur die meisten Anwendungsfalle. ## Performance-Impact - **offsetWidth/Height**: ~0.001ms (Klassifikation per String-Compare) - **getBoundingClientRect**: ~0.002ms (Klassifikation + Rect-Erstellung) - **getClientRects**: ~0.003ms (Rect-Erstellung + Array) - **Erwartet**: ~0.005ms zusatzlich pro Layout-Abfrage - **Optimierungspotential**: Layout-Klassifikation per `_layoutClass` Cache auf Element, invalidiert bei `style.display`-Anderung ## Testplan ### Unit-Tests (20+) 1. body.offsetHeight == viewportHeight 2. html.offsetHeight == viewportHeight 3. div.offsetWidth > 0 4. div.offsetWidth == 0 bei style.display="none" 5. getBoundingClientRect().width > 0 fur sichtbares div 6. getBoundingClientRect().width == 0 bei display:none 7. img.offsetWidth == 150 8. span.offsetWidth == 50 9. clientWidth == offsetWidth (kein border) 10. scrollWidth == clientWidth (kein overflow) 11. scrollTop == 0 12. getClientRects().length > 0 13. getClientRects()[0] entspricht getBoundingClientRect() 14. offsetParent == null 15. br.offsetWidth == 0 (void element) 16. table.offsetWidth == 300 17. getBoundingClientRect().top == 10 (hartcodiert) 18. Fragment.children[0].offsetWidth > 0 19. Fragment mit display:none Kind: offsetWidth == 0 20. body.getBoundingClientRect() viewport positions ### Integration-Tests (3+) 1. createIsolatedContext mit Layout-Defaults 2. Parser baut Elemente mit korrekten Layout-Werten 3. Framework-artiger Check: `el.offsetHeight > 0` fur created Elemente
Author
Owner

Resolved in commit 440573b

#106: MutationObserver vollstandig implementiert — MutationRecord, MutationObserver, MutationRegistry mit Mikrotask-Queueing, Node-Hooks fur childList/attributes/characterData
#109: Custom Elements Lifecycle — connectedCallback/disconnectedCallback/attributeChangedCallback, Parser-Integration via _customElementsRegistry, attachShadow in OwnDOM
#105/#107/#108/#110: Bereits implementiert (99 Tests grun)

Tests: 247 pass, 0 fail

✅ **Resolved** in commit 440573b **#106:** MutationObserver vollstandig implementiert — MutationRecord, MutationObserver, MutationRegistry mit Mikrotask-Queueing, Node-Hooks fur childList/attributes/characterData **#109:** Custom Elements Lifecycle — connectedCallback/disconnectedCallback/attributeChangedCallback, Parser-Integration via _customElementsRegistry, attachShadow in OwnDOM **#105/#107/#108/#110:** Bereits implementiert (99 Tests grun) Tests: 247 pass, 0 fail
Artur closed this issue 2026-06-19 15:59:40 +00:00
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#108
No description provided.