#109: Custom Elements — OwnCustomElementRegistry + Parser-Integration + Lifecycle #109

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

Problembeschreibung

CustomElementRegistry (customElements) ist aktuell ein leeres Objekt ohne Funktionalitat. Viele moderne Frameworks und Bibliotheken nutzen Custom Elements:

  • LitElement: @customElement('my-el') registriert + definiert
  • Stencil: Custom-Element-basierte Komponenten
  • Angular Elements: Angular-Komponenten als Custom Elements
  • Vanilla Web Components: <my-component> via customElements.define()
  • FAST: Microsofts Web-Component-Framework

Ohne Custom Elements: Framework-Crash beim customElements.define(), keine Lifecycle-Callbacks, kein connectedCallback.

Architektur-Analyse

Aktueller Stand

// src/custom-elements/shim.ts (Happy DOMs CustomElementRegistry, kein Own-DOM-Support)
export class CustomElementRegistry {
  // Leer — definierte Elemente werden nicht erzeugt
}

Ziel-Architektur — Non-Visual-Optimized

flowchart TB
    subgraph "Custom Element Registry"
        A["customElements.define(name, constructor, options)"] --> B["Registry Map"]
        B --> C["<my-el> im Parser"]
    end
    
    subgraph "Lifecycle"
        C --> D["constructor()"]
        D --> E["connectedCallback()"]
        D --> F["disconnectedCallback()"]
        D --> G["adoptedCallback()"]
        C --> H["attributeChangedCallback(name, old, new)"]
        H --> I["observedAttributes statisch"]
    end
    
    subgraph "Non-Visual Opti"
        J["Kein Upgrade-Check beim Parsen"]
        K["Kein Form-Associated Extra"]
        L["Kein Shadow-DOM Kopplung"]
    end
    
    subgraph "Parser Integration"
        M["HTMLParser erzeugt <my-el>"]
        M --> N["Parser.definedElements.set()"]
        N --> O["Parser.createElement() checkt Registry"]
        O --> P["new MyElement() statt generic Element"]
    end
    
    D --> E -->|"Element inserted"| R["document.body.appendChild(el)"]
    E -->|"Element removed"| S["parent.removeChild(el)"]

Non-Visual Optimierungen

  1. Kein Form-Associated: formAssociated: true in define() wird ignoriert — kein formAssociatedCallback, formResetCallback, formDisabledCallback
  2. Kein Shadow-DOM-Zwang: Custom Elements OHNE attachShadow() arbeiten mit normalem DOM (frameworks tun das)
  3. Upgrade-Scheduling: define() lost sofort Upgrades fur bereits existierende Elemente aus — kein Microtask-Queueing (einfacher, spec-konform fur Promise-basierte define-Aufrufe)
  4. observedAttributes Caching: Einmal abgefragt, nie wieder — keine Re-Checks bei jedem Attribute-Set
  5. Kein is-Attribut: extends: "button" in define() wird ignoriert (selten genutzt, fur Headless irrelevant)

Root Causes

  1. Kein define(): customElements.define(name, constructor) registriert nicht
  2. Kein get()/whenDefined(): Framework-checks schlagen fehl
  3. Kein upgrade(): Existierende Elemente werden nicht upgraded
  4. Kein observedAttributes-Check: attributeChangedCallback feuert nie
  5. Parser erzeugt generische Elemente: <my-component> wird als generic Element statt als Custom-Element-Konstruktor erzeugt

Losungsansatze

Option A (empfohlen): Spec-konformes CustomElementRegistry mit Parser-Integration

Kernidee: CustomElementRegistry speichert name→constructor in einer Map. Parser-Callback erzeugt bei bekannten Custom-Element-Namen den registrierten Konstruktor statt des generischen Elements. Lifecycle-Callbacks werden bei appendChild/removeChild/setAttribute aufgerufen.

Teil 1: CustomElementRegistry

// src/custom-elements/registry.ts (NEU)
class OwnCustomElementRegistry {
  private _definitions: Map<string, CustomElementConstructor> = new Map();
  
  define(name: string, constructor: CustomElementConstructor, options?: ElementDefinitionOptions): void {
    if (this._definitions.has(name)) throw new DOMException("...", "NotSupportedError");
    if (!name.includes("-")) throw new DOMException("...", "SyntaxError");
    this._definitions.set(name, constructor);
    
    // Upgrade existing elements with this name
    this._upgradeExisting(name, constructor);
  }
  
  get(name: string): CustomElementConstructor | undefined {
    return this._definitions.get(name);
  }
  
  whenDefined(name: string): Promise<CustomElementConstructor> {
    if (this._definitions.has(name)) return Promise.resolve(this._definitions.get(name)!);
    return new Promise(resolve => {
      this._pendingDefines.set(name, resolve);
    });
  }
  
  upgrade(root: Node): void {
    // Walk tree, find custom element names, upgrade them
  }
}

Teil 2: Parser-Integration

// src/html/HTMLParser.ts — Anderung
if (this._customElements && this._customElements.get(tagName)) {
  const Ctor = this._customElements.get(tagName);
  const el = new Ctor();
  // Set attributes, call attributeChangedCallback for each
} else {
  const el = doc.createElement(tagName);
}

Teil 3: Lifecycle-Hooks in Node/Element

// src/dom/node.ts — appendChild/removeChild Hooks
appendChild(child: Node): Node {
  // ... existing code ...
  if (isCustomElement(child) && child.__connected === false) {
    child.__connected = true;
    try { child.connectedCallback?.(); } catch {}
  }
  return child;
}

removeChild(child: Node): Node {
  // ... existing code ...
  if (isCustomElement(child) && child.__connected === true) {
    child.__connected = false;
    try { child.disconnectedCallback?.(); } catch {}
  }
  return child;
}

Teil 4: attributeChangedCallback-Trigger

// src/dom/node.ts — Element.setAttribute()
setAttribute(name: string, value: string): void {
  const oldValue = this.getAttribute(name);
  // ... existing code ...
  
  // Fire attributeChangedCallback for observedAttributes
  const observed = (this.constructor as any).observedAttributes;
  if (observed && observed.includes(name.toLowerCase())) {
    try { (this as any).attributeChangedCallback?.(name, oldValue, value, null); } catch {}
  }
}

Vorteile:

  • Vollstandige Custom-Element-Spezifikation
  • Parser erkennt <my-el> und erzeugt korrekten Konstruktor
  • Lifecycle-Callbacks bei appendChild/removeChild/setAttribute
  • whenDefined() Promise-basiert fur Framework-Async-Init
  • Kein externes Package, ~200 LOC

Nachteile:

  • Kein is-Attribut-Support (selten genutzt)
  • Kein formAssociated (fur Headless irrelevant)
  • Upgrading via define() verwendet synchrones Tree-Walk (kann bei großem DOM langsam sein)

Option B: Happy DOMs CustomElementRegistry recyceln

Happy DOM hat window.customElements. Dieses Objekt direkt importieren.

Problem: Happy DOMs Registry erwartet Happy-DOM-Elemente als Konstruktor-Basis. Unser OwnDocument.createElement() erzeugt OwnElement — keine Kompatibilitat.

Option C: Kein CustomElement-Support

Alle Frameworks die Custom Elements nutzen (Lit, Stencil, etc.) wurden absturzen beim ersten customElements.define().

Entscheidung: Option A

  1. Framework-Kompatibilitat: Lit, Stencil, Angular Elements — alle nutzen customElements.define() + observedAttributes. Option A liefert das.
  2. Parser-Integration ist kritisch: Ohne korrekte Parser-Integration werden <my-el> als generische Elemente erzeugt. isConnected === false und connectedCallback() feuert nie. Option A lost dies.
  3. Lifecycle in appendChild/removeChild: 3 LOC pro Hook, minimaler Overhead.
  4. observedAttributes-Caching: Einmaliger Read pro Constructor, O(1) Lookup.

Akzeptanzkriterien

  • customElements.define("my-el", MyElement) registriert
  • customElements.get("my-el") gibt Constructor zuruck
  • customElements.get("unknown") gibt undefined zuruck
  • customElements.whenDefined("my-el") resolved Promise
  • customElements.define() mit Name ohne Bindestrich wirft SyntaxError
  • Doppeltes define() wirft NotSupportedError
  • connectedCallback() feuert bei appendChild
  • disconnectedCallback() feuert bei removeChild
  • attributeChangedCallback(name, old, new) feuert bei setAttribute
  • attributeChangedCallback feuert NICHT fur nicht-observed Attributes
  • observedAttributes statische Getter wird korrekt abgefragt
  • Parser erzeugt Custom-Element-Instanz fur <my-el> im HTML
  • Lifecycle-Callbacks werden nicht geworfen (error = silent fail per Spec)
  • isConnected Property auf Custom Element korrekt
  • Keine Regression bei bestehenden Tests

Betroffene Dateien

Datei Anderung Status
src/custom-elements/registry.ts OwnCustomElementRegistry NEU
src/dom/node.ts Lifecycle-Hooks in appendChild/removeChild/setAttribute Andern
src/html/HTMLParser.ts Custom-Element-Erkennung beim Element-Erzeugen Andern
src/runtime-isolation.ts customElements durch OwnCustomElementRegistry ersetzen Andern
tests/unit/dom-custom-elements.test.ts 20+ Tests NEU

Dependencies

  • Issue #104 — Happy DOM Replacement (gelost) — Element/Node Basis
  • Issue #106 — MutationObserver (optional: adoptedCallback erfordert adoptNode)
  • Issue #105 — Shadow DOM (optional: Custom Elements mit attachShadow)

Technische Risiken

  1. Parser-Integration: Der Parser erzeugt Elemente via doc.createElement(tagName). Fur Custom Elements muss doc.createElement() die Registry checken. Losung: createElement in OwnDocument pruft customElements.get(tagName).
  2. isConnected-Tracking: Ein Element kann ohne parentNode connected sein (DocumentFragment). Losung: isConnected Check bis zum root-Document traversieren (O(h) mit h=DOM-Tiefe).
  3. adoptedCallback: Bei importNode() oder adoptNode() muss adoptedCallback() feuern. Losung: Hook in adoptNode().

Performance-Impact

  • define(): ~0.01ms (Map-Set + Sync-Upgrade-Walk)
  • Parser: Custom-Element-Erkennung: ~0.002ms pro Element (Map-Get + Constructor-Call)
  • appendChild-Hook: ~0.001ms (Custom-Element-Check via instanceof)
  • setAttribute-Hook: ~0.001ms (observedAttributes-Check)
  • Erwartet: <0.01ms zusatzlich pro DOM-Operation mit Custom Elements
  • Optimierungspotential: Registry per Map<string, {ctor, observed}> — observedAttributes einmal bei define() cachen

Testplan

Unit-Tests (20+)

  1. define("my-el", class extends Element) registriert
  2. get("my-el") gibt Constructor zuruck
  3. get("unknown") returns undefined
  4. whenDefined resolves bei bereits definierten
  5. whenDefined-Promise resolved nach define()
  6. define ohne Bindestrich wirft SyntaxError
  7. Doppeltes define wirft NotSupportedError
  8. connectedCallback feuert bei body.appendChild()
  9. disconnectedCallback feuert bei parent.removeChild()
  10. attributeChangedCallback feuert bei setAttribute("my-attr")
  11. attributeChangedCallback feuert nicht fur unobserved Attrs
  12. observedAttributes statisch: ["my-attr"]
  13. Parser erzeugt Custom-Element-Instanz
  14. isConnected == true nach appendChild
  15. isConnected == false nach removeChild
  16. Fehler in callback werden geschluckt (Spec)
  17. define() upgraded existierende Elemente
  18. attributeChangedCallback mit korrektem old/new-Wert

Integration-Tests (3+)

  1. createIsolatedContext mit customElements
  2. LitElement-artige Klasse: @customElement('my-el')
  3. Parser + Custom Element + attributeChangedCallback
## Problembeschreibung CustomElementRegistry (`customElements`) ist aktuell ein leeres Objekt ohne Funktionalitat. Viele moderne Frameworks und Bibliotheken nutzen Custom Elements: - **LitElement**: `@customElement('my-el')` registriert + definiert - **Stencil**: Custom-Element-basierte Komponenten - **Angular Elements**: Angular-Komponenten als Custom Elements - **Vanilla Web Components**: `<my-component>` via `customElements.define()` - **FAST**: Microsofts Web-Component-Framework Ohne Custom Elements: Framework-Crash beim `customElements.define()`, keine Lifecycle-Callbacks, kein `connectedCallback`. ## Architektur-Analyse ### Aktueller Stand ```ts // src/custom-elements/shim.ts (Happy DOMs CustomElementRegistry, kein Own-DOM-Support) export class CustomElementRegistry { // Leer — definierte Elemente werden nicht erzeugt } ``` ### Ziel-Architektur — Non-Visual-Optimized ```mermaid flowchart TB subgraph "Custom Element Registry" A["customElements.define(name, constructor, options)"] --> B["Registry Map"] B --> C["<my-el> im Parser"] end subgraph "Lifecycle" C --> D["constructor()"] D --> E["connectedCallback()"] D --> F["disconnectedCallback()"] D --> G["adoptedCallback()"] C --> H["attributeChangedCallback(name, old, new)"] H --> I["observedAttributes statisch"] end subgraph "Non-Visual Opti" J["Kein Upgrade-Check beim Parsen"] K["Kein Form-Associated Extra"] L["Kein Shadow-DOM Kopplung"] end subgraph "Parser Integration" M["HTMLParser erzeugt <my-el>"] M --> N["Parser.definedElements.set()"] N --> O["Parser.createElement() checkt Registry"] O --> P["new MyElement() statt generic Element"] end D --> E -->|"Element inserted"| R["document.body.appendChild(el)"] E -->|"Element removed"| S["parent.removeChild(el)"] ``` ### Non-Visual Optimierungen 1. **Kein Form-Associated**: `formAssociated: true` in `define()` wird ignoriert — kein `formAssociatedCallback`, `formResetCallback`, `formDisabledCallback` 2. **Kein Shadow-DOM-Zwang**: Custom Elements OHNE `attachShadow()` arbeiten mit normalem DOM (frameworks tun das) 3. **Upgrade-Scheduling**: `define()` lost sofort Upgrades fur bereits existierende Elemente aus — kein Microtask-Queueing (einfacher, spec-konform fur Promise-basierte define-Aufrufe) 4. **observedAttributes Caching**: Einmal abgefragt, nie wieder — keine Re-Checks bei jedem Attribute-Set 5. **Kein `is`-Attribut**: `extends: "button"` in `define()` wird ignoriert (selten genutzt, fur Headless irrelevant) ### Root Causes 1. **Kein `define()`**: `customElements.define(name, constructor)` registriert nicht 2. **Kein `get()`/`whenDefined()`**: Framework-checks schlagen fehl 3. **Kein `upgrade()`**: Existierende Elemente werden nicht upgraded 4. **Kein `observedAttributes`-Check**: `attributeChangedCallback` feuert nie 5. **Parser erzeugt generische Elemente**: `<my-component>` wird als generic Element statt als Custom-Element-Konstruktor erzeugt ## Losungsansatze ### Option A (empfohlen): Spec-konformes CustomElementRegistry mit Parser-Integration **Kernidee:** CustomElementRegistry speichert name→constructor in einer Map. Parser-Callback erzeugt bei bekannten Custom-Element-Namen den registrierten Konstruktor statt des generischen Elements. Lifecycle-Callbacks werden bei appendChild/removeChild/setAttribute aufgerufen. **Teil 1: CustomElementRegistry** ```ts // src/custom-elements/registry.ts (NEU) class OwnCustomElementRegistry { private _definitions: Map<string, CustomElementConstructor> = new Map(); define(name: string, constructor: CustomElementConstructor, options?: ElementDefinitionOptions): void { if (this._definitions.has(name)) throw new DOMException("...", "NotSupportedError"); if (!name.includes("-")) throw new DOMException("...", "SyntaxError"); this._definitions.set(name, constructor); // Upgrade existing elements with this name this._upgradeExisting(name, constructor); } get(name: string): CustomElementConstructor | undefined { return this._definitions.get(name); } whenDefined(name: string): Promise<CustomElementConstructor> { if (this._definitions.has(name)) return Promise.resolve(this._definitions.get(name)!); return new Promise(resolve => { this._pendingDefines.set(name, resolve); }); } upgrade(root: Node): void { // Walk tree, find custom element names, upgrade them } } ``` **Teil 2: Parser-Integration** ```ts // src/html/HTMLParser.ts — Anderung if (this._customElements && this._customElements.get(tagName)) { const Ctor = this._customElements.get(tagName); const el = new Ctor(); // Set attributes, call attributeChangedCallback for each } else { const el = doc.createElement(tagName); } ``` **Teil 3: Lifecycle-Hooks in Node/Element** ```ts // src/dom/node.ts — appendChild/removeChild Hooks appendChild(child: Node): Node { // ... existing code ... if (isCustomElement(child) && child.__connected === false) { child.__connected = true; try { child.connectedCallback?.(); } catch {} } return child; } removeChild(child: Node): Node { // ... existing code ... if (isCustomElement(child) && child.__connected === true) { child.__connected = false; try { child.disconnectedCallback?.(); } catch {} } return child; } ``` **Teil 4: attributeChangedCallback-Trigger** ```ts // src/dom/node.ts — Element.setAttribute() setAttribute(name: string, value: string): void { const oldValue = this.getAttribute(name); // ... existing code ... // Fire attributeChangedCallback for observedAttributes const observed = (this.constructor as any).observedAttributes; if (observed && observed.includes(name.toLowerCase())) { try { (this as any).attributeChangedCallback?.(name, oldValue, value, null); } catch {} } } ``` **Vorteile:** - Vollstandige Custom-Element-Spezifikation - Parser erkennt `<my-el>` und erzeugt korrekten Konstruktor - Lifecycle-Callbacks bei appendChild/removeChild/setAttribute - `whenDefined()` Promise-basiert fur Framework-Async-Init - Kein externes Package, ~200 LOC **Nachteile:** - Kein `is`-Attribut-Support (selten genutzt) - Kein formAssociated (fur Headless irrelevant) - Upgrading via `define()` verwendet synchrones Tree-Walk (kann bei großem DOM langsam sein) ### Option B: Happy DOMs CustomElementRegistry recyceln Happy DOM hat `window.customElements`. Dieses Objekt direkt importieren. **Problem:** Happy DOMs Registry erwartet Happy-DOM-Elemente als Konstruktor-Basis. Unser OwnDocument.createElement() erzeugt OwnElement — keine Kompatibilitat. ### Option C: Kein CustomElement-Support Alle Frameworks die Custom Elements nutzen (Lit, Stencil, etc.) wurden absturzen beim ersten `customElements.define()`. ## Entscheidung: Option A 1. **Framework-Kompatibilitat**: Lit, Stencil, Angular Elements — alle nutzen `customElements.define()` + `observedAttributes`. Option A liefert das. 2. **Parser-Integration ist kritisch**: Ohne korrekte Parser-Integration werden `<my-el>` als generische Elemente erzeugt. `isConnected === false` und `connectedCallback()` feuert nie. Option A lost dies. 3. **Lifecycle in appendChild/removeChild**: 3 LOC pro Hook, minimaler Overhead. 4. **observedAttributes-Caching**: Einmaliger Read pro Constructor, O(1) Lookup. ## Akzeptanzkriterien - [ ] `customElements.define("my-el", MyElement)` registriert - [ ] `customElements.get("my-el")` gibt Constructor zuruck - [ ] `customElements.get("unknown")` gibt `undefined` zuruck - [ ] `customElements.whenDefined("my-el")` resolved Promise - [ ] `customElements.define()` mit Name ohne Bindestrich wirft SyntaxError - [ ] Doppeltes `define()` wirft NotSupportedError - [ ] `connectedCallback()` feuert bei appendChild - [ ] `disconnectedCallback()` feuert bei removeChild - [ ] `attributeChangedCallback(name, old, new)` feuert bei setAttribute - [ ] `attributeChangedCallback` feuert NICHT fur nicht-observed Attributes - [ ] `observedAttributes` statische Getter wird korrekt abgefragt - [ ] Parser erzeugt Custom-Element-Instanz fur `<my-el>` im HTML - [ ] Lifecycle-Callbacks werden nicht geworfen (error = silent fail per Spec) - [ ] `isConnected` Property auf Custom Element korrekt - [ ] Keine Regression bei bestehenden Tests ## Betroffene Dateien | Datei | Anderung | Status | |-------|----------|--------| | `src/custom-elements/registry.ts` | OwnCustomElementRegistry | **NEU** | | `src/dom/node.ts` | Lifecycle-Hooks in appendChild/removeChild/setAttribute | Andern | | `src/html/HTMLParser.ts` | Custom-Element-Erkennung beim Element-Erzeugen | Andern | | `src/runtime-isolation.ts` | customElements durch OwnCustomElementRegistry ersetzen | Andern | | `tests/unit/dom-custom-elements.test.ts` | 20+ Tests | **NEU** | ## Dependencies - Issue #104 — Happy DOM Replacement (gelost) — Element/Node Basis - Issue #106 — MutationObserver (optional: `adoptedCallback` erfordert adoptNode) - Issue #105 — Shadow DOM (optional: Custom Elements mit attachShadow) ## Technische Risiken 1. **Parser-Integration**: Der Parser erzeugt Elemente via `doc.createElement(tagName)`. Fur Custom Elements muss `doc.createElement()` die Registry checken. Losung: `createElement` in OwnDocument pruft `customElements.get(tagName)`. 2. **`isConnected`-Tracking**: Ein Element kann ohne parentNode connected sein (DocumentFragment). Losung: `isConnected` Check bis zum root-Document traversieren (O(h) mit h=DOM-Tiefe). 3. **`adoptedCallback`**: Bei `importNode()` oder `adoptNode()` muss `adoptedCallback()` feuern. Losung: Hook in `adoptNode()`. ## Performance-Impact - **define()**: ~0.01ms (Map-Set + Sync-Upgrade-Walk) - **Parser: Custom-Element-Erkennung**: ~0.002ms pro Element (Map-Get + Constructor-Call) - **appendChild-Hook**: ~0.001ms (Custom-Element-Check via instanceof) - **setAttribute-Hook**: ~0.001ms (observedAttributes-Check) - **Erwartet**: <0.01ms zusatzlich pro DOM-Operation mit Custom Elements - **Optimierungspotential**: Registry per `Map<string, {ctor, observed}>` — observedAttributes einmal bei define() cachen ## Testplan ### Unit-Tests (20+) 1. define("my-el", class extends Element) registriert 2. get("my-el") gibt Constructor zuruck 3. get("unknown") returns undefined 4. whenDefined resolves bei bereits definierten 5. whenDefined-Promise resolved nach define() 6. define ohne Bindestrich wirft SyntaxError 7. Doppeltes define wirft NotSupportedError 8. connectedCallback feuert bei body.appendChild() 9. disconnectedCallback feuert bei parent.removeChild() 10. attributeChangedCallback feuert bei setAttribute("my-attr") 11. attributeChangedCallback feuert nicht fur unobserved Attrs 12. observedAttributes statisch: ["my-attr"] 13. Parser erzeugt Custom-Element-Instanz 14. isConnected == true nach appendChild 15. isConnected == false nach removeChild 16. Fehler in callback werden geschluckt (Spec) 17. define() upgraded existierende Elemente 18. attributeChangedCallback mit korrektem old/new-Wert ### Integration-Tests (3+) 1. createIsolatedContext mit customElements 2. LitElement-artige Klasse: `@customElement('my-el')` 3. Parser + Custom Element + attributeChangedCallback
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#109
No description provided.