Resource Blocking + Unified Network Pipeline — PageNetworkManager als Single Entry Point #113

Closed
opened 2026-06-19 20:55:55 +00:00 by Artur · 0 comments
Owner

🔍 Korrigierte Problem-Analyse

Issue #113 wurde nach Code-Review aktualisiert. Die ursprüngliche Annahme „Bilder/CSS/Fonts laden ungefiltert" ist falsch — diese sind bereits via DOM-Level-Fakes geblockt.

Tatsächlicher Ist-Zustand

Resource Lädt Netzwerk? Via
<img src> NEIN Fake src-setter in src/fakes/media.ts → sofort onload
<link href=.css> NEIN Fake href-setter → sofort onload
<style> / FontFace NEIN Fake FontFace.loaded = Promise.resolve()
<video>/<audio> NEIN Fake readyState=4, play() resolved sofort
<script src> JA fetch(url) in script-loader.ts:346Bun globales fetch, bypassed gesamte Pipeline
fetch(/api) via page JS JA Geht durch createFetch()skipResources (aber redundant — trifft nur image/css/media URLs)

Das echte Problem

Script-Loading umgeht die gesamte Network-Pipeline:

flowchart LR
    subgraph AKTUELL
        S1[ScriptLoader.fetchContent] -->|fetch&#40;url&#41;| B1[Bun.global.fetch]
        B1 --> N1[HTTP ohne CookieJar]
        B1 --> N2[HTTP ohne Security]
        B1 --> N3[HTTP ohne Interceptors]
    end

    subgraph SOLL
        S2[ScriptLoader.fetchContent] --> R1[PageNetworkManager.request]
        R1 --> C1[CookieJar ✓]
        R1 --> C2[SecurityManager ✓]
        R1 --> C3[Interceptors ✓]
        R1 --> C4[Resource Blocking ✓]
        R1 --> T1[createFetch / curl]
    end

Konsequenzen:

  • Tracking/Analytics-Skripte (GA4, Meta-Pixel, Hotjar) können nicht selektiv geblockt werden
  • XHR-initiated Media-Downloads (fetch(/video.mp4)) werden nicht gefaked
  • Cookies fehlen bei Script-Requests → CDNs/Login-Gates failen
  • Interceptors unwirksam für Script-URLs
  • Keine zentrale Metrik für Request-Volumen

Lösung

1. shouldBlockRequest Callback in PageNetworkManager

// Jeder Request durchläuft diesen Filter VOR dem Transport
// Bilder/Fonts/CSS/Media: default-blocked (durch media.ts bereits fake, hier doppelt abgesichert)
// Tracking-URLs: user-konfigurierbar
type ShouldBlockRequest = (req: { url: string; type: ResourceType; method: string }) => boolean;

export type ResourceType = "document" | "script" | "stylesheet" | "image" | "font" | "media" | "xhr" | "websocket" | "other";

2. blockResourceTypes in PageOptions

export interface PageOptions {
  /**
   * Resource-Typen, die geblockt werden sollen.
   * Default: [image, font, media, stylesheet]
   * Scripts sind NICHT im Default — wir brauchen sie für JS-Ausführung.
   * User kann selbst entscheiden ob Tracking-Skripte blockiert werden.
   */
  blockResourceTypes?: ResourceType[];

  /** Individuelle Block-Entscheidung pro Request (höchste Priorität) */
  shouldBlockRequest?: ShouldBlockRequest;
}

3. ScriptLoader → PageNetworkManager Integration

fetchContent() ruft this.network.request(url, GET) statt fetch(url):

async fetchContent(url: string): Promise<string> {
  if (this.preloadCache.has(url)) { /* ... */ }
  if (this.runtimeCache.get(url)) return /* ... */;

  try {
    // NEU: Geht durch PageNetworkManager → CookieJar → Interceptors → Blocking → Transport
    const response = await this.network.request(url, GET, { transport: script });
    const text = await response.text();
    /* ... */
  } catch (err) { /* ... */ }
}

4. RequestManager Resource-Type Detection

Via URL + Kontext:

  • .jsscript
  • .cssstylesheet
  • .jpg/.png/.webpimage
  • .woff/.woff2/.ttffont
  • .mp4/.webmmedia
  • .html (via XHR) → xhr
  • rest → other

5. Default Block-List (Konservativ)

// Immer geblockt (schon via DOM-Fakes, hier doppelt abgesichert):
const DEFAULT_BLOCKED: ResourceType[] = [image, font, media, stylesheet];

// User kann erweitern:
new Page({ blockResourceTypes: [...DEFAULT_BLOCKED, script] });
// → Blockiert ALLE Scripts, auch GA4/Pixel (aber bricht Seite!)

// Selective blocking via Callback:
new Page({
  shouldBlockRequest: (req) => 
    req.type === script && 
    (req.url.includes(google-analytics) || req.url.includes(facebook.net))
});

API-Änderungen

RequestManager (neu)

class RequestManager {
  /** Check ob ein Request geblockt werden soll */
  shouldBlock(url: string, type: ResourceType, method: string): boolean {}

  /** Block-Counter */
  get blockedCount(): number {}

  get metrics(): { total: number; blocked: number; byType: Record<string, number> } {}
}

Page (neu)

class Page {
  /** Anzahl geblockter Requests */
  get blockedRequests(): number {}

  /** Detaillierte Metrics */
  get networkMetrics(): { total: number; blocked: number; byType: Record<string, number> } {}
}

Akzeptanzkriterien

  • DONE: Media-Fakes (images/css/fonts/video) blockieren bereits Netzwerk
  • blockResourceTypes in PageOptions (Default: [image, font, media, stylesheet])
  • shouldBlockRequest(req) → boolean Callback (höchste Priorität)
  • RequestManager: shouldBlock(url, type, method) → boolean
  • RequestManager: blockedCount, metrics Property
  • ScriptLoader.fetchContent → PageNetworkManager.request() (KEIN direktes fetch())
  • ScriptLoader kriegt network Reference via Constructor oder Setter
  • Transport-Typ script für Script-Requests
  • Blockierte Requests → new Response(, { status: 200 }) ohne Netzwerk
  • Blocked Counter wird pro Navigation resettet
  • Default blockList blockiert Images/Fonts/Media/CSS (redundant zu media.ts, aber Absicherung)
  • Scripts sind NICHT im Default blockList
  • page.metrics().blockedRequests zählt korrekt
  • Cross-Origin Requests werden auch geblockt
  • Leeres Array = keine Blockierung

Betroffene Dateien

Datei Änderung Aufwand
src/network/request-manager.ts shouldBlock(), blockResourceTypes, shouldBlockRequest callback, metrics, blockedCount ~60 Zeilen
src/network/network-manager.ts PageNetworkManager.request() ruft shouldBlock() auf ~15 Zeilen
src/js/script-loader.ts network Reference, fetchContent() via this.network.request() ~20 Zeilen
src/pages/page.ts PageOptions.blockResourceTypes, shouldBlockRequest, networkMetrics(), blockedRequests getter ~25 Zeilen
src/runtime-isolation.ts ContextOptions.blockResourceTypes, shouldBlockRequest durchreichen ~10 Zeilen
tests/unit/resource-blocking.test.ts NEU: 20+ Tests ~400 Zeilen

Performance Impact (korrigiert)

Szenario Requests Speedup (Script-Blocking)
amazon.de (kein Block) ~200 Req (davon ~160 Scripts/Bilder) 1x
amazon.de (media.fakes) ~40 Req (nur Scripts+HTML) ~5x (schon erreicht!)
amazon.de (media.fakes + tracking block) ~30 Req (ohne GA4/GTM) ~6x

Das issue ist primär Pipeline-Integration, nicht Bandbreiten-Ersparnis — die 80% sind schon da.

Testplan (20 Tests)

# Test Typ
RB01 shouldBlock: image URL → true Unit
RB02 shouldBlock: font URL → true Unit
RB03 shouldBlock: media URL → true Unit
RB04 shouldBlock: script URL → false (default) Unit
RB05 shouldBlock: custom blockResourceTypes=[script] Unit
RB06 shouldBlockRequest Callback override Unit
RB07 blockedCount incrementiert bei Block Unit
RB08 metrics.blocked = total blocked Unit
RB09 metrics.byType gruppiert korrekt Unit
RB10 Leeres blockResourceTypes = nix blockiert Unit
RB11 ScriptLoader.fetchContent → PageNetworkManager (Integration) Unit
RB12 Blocked Request → 200 OK Unit
RB13 Blocked Request → Empty Body Unit
RB14 Cross-Origin Block Integration
RB15 Block-List resettet bei neuem goto() Integration
RB16 page.networkMetrics() liefert korrekte Werte Integration
RB17 Script NOT blocked by default Integration
RB18 shouldBlockRequest erhält korrekte Parameter (url, type, method) Unit
RB19 fetch() via page JS: blockResourceTypes greift auch hier Integration
RB20 Mixed: image block + scripts allowed Integration

Phase 1: RequestManager + PageNetworkManager Blocking-Logik (RB01-RB10)
Phase 2: ScriptLoader Integration (RB11)
Phase 3: Page-Level + Integrationstests (RB12-RB20)

Risks

Risk Impact Mitigation
ScriptLoader.fetchContent bricht bei PageNetworkManager-Fehler Hoch Fallback zu fetch(url) im Catch
Default blockList blockiert versehentlich notwendige Scripts Hoch Default blockList enthält NICHT script — nur image/font/media/stylesheet
shouldBlockRequest wirkt auch auf HTML-Navigation Mittel Navigation hat type=document → nicht im Default
WebSocket wird geblockt Mittel WS separat behandeln (type=websocket nicht im Default)
iframe-Kompatibilität Mittel type=document nicht blocken
## 🔍 Korrigierte Problem-Analyse Issue #113 wurde nach Code-Review aktualisiert. Die ursprüngliche Annahme „Bilder/CSS/Fonts laden ungefiltert" ist **falsch** — diese sind bereits via DOM-Level-Fakes geblockt. ### Tatsächlicher Ist-Zustand | Resource | Lädt Netzwerk? | Via | |---|---|---| | `<img src>` | ❌ **NEIN** | Fake src-setter in `src/fakes/media.ts` → sofort `onload` | | `<link href=.css>` | ❌ **NEIN** | Fake href-setter → sofort `onload` | | `<style>` / FontFace | ❌ **NEIN** | Fake `FontFace.loaded = Promise.resolve()` | | `<video>/<audio>` | ❌ **NEIN** | Fake `readyState=4`, play() resolved sofort | | **`<script src>`** | ✅ **JA** | `fetch(url)` in `script-loader.ts:346` — **Bun globales fetch, bypassed gesamte Pipeline** | | `fetch(/api)` via page JS | ✅ JA | Geht durch `createFetch()` → `skipResources` (aber redundant — trifft nur image/css/media URLs) | ### Das echte Problem **Script-Loading umgeht die gesamte Network-Pipeline:** ```mermaid flowchart LR subgraph AKTUELL S1[ScriptLoader.fetchContent] -->|fetch&#40;url&#41;| B1[Bun.global.fetch] B1 --> N1[HTTP ohne CookieJar] B1 --> N2[HTTP ohne Security] B1 --> N3[HTTP ohne Interceptors] end subgraph SOLL S2[ScriptLoader.fetchContent] --> R1[PageNetworkManager.request] R1 --> C1[CookieJar ✓] R1 --> C2[SecurityManager ✓] R1 --> C3[Interceptors ✓] R1 --> C4[Resource Blocking ✓] R1 --> T1[createFetch / curl] end ``` **Konsequenzen:** - Tracking/Analytics-Skripte (GA4, Meta-Pixel, Hotjar) können nicht selektiv geblockt werden - XHR-initiated Media-Downloads (`fetch(/video.mp4)`) werden nicht gefaked - Cookies fehlen bei Script-Requests → CDNs/Login-Gates failen - Interceptors unwirksam für Script-URLs - Keine zentrale Metrik für Request-Volumen ## Lösung ### 1. `shouldBlockRequest` Callback in PageNetworkManager ```ts // Jeder Request durchläuft diesen Filter VOR dem Transport // Bilder/Fonts/CSS/Media: default-blocked (durch media.ts bereits fake, hier doppelt abgesichert) // Tracking-URLs: user-konfigurierbar type ShouldBlockRequest = (req: { url: string; type: ResourceType; method: string }) => boolean; export type ResourceType = "document" | "script" | "stylesheet" | "image" | "font" | "media" | "xhr" | "websocket" | "other"; ``` ### 2. `blockResourceTypes` in PageOptions ```ts export interface PageOptions { /** * Resource-Typen, die geblockt werden sollen. * Default: [image, font, media, stylesheet] * Scripts sind NICHT im Default — wir brauchen sie für JS-Ausführung. * User kann selbst entscheiden ob Tracking-Skripte blockiert werden. */ blockResourceTypes?: ResourceType[]; /** Individuelle Block-Entscheidung pro Request (höchste Priorität) */ shouldBlockRequest?: ShouldBlockRequest; } ``` ### 3. ScriptLoader → PageNetworkManager Integration `fetchContent()` ruft `this.network.request(url, GET)` statt `fetch(url)`: ```ts async fetchContent(url: string): Promise<string> { if (this.preloadCache.has(url)) { /* ... */ } if (this.runtimeCache.get(url)) return /* ... */; try { // NEU: Geht durch PageNetworkManager → CookieJar → Interceptors → Blocking → Transport const response = await this.network.request(url, GET, { transport: script }); const text = await response.text(); /* ... */ } catch (err) { /* ... */ } } ``` ### 4. RequestManager Resource-Type Detection Via URL + Kontext: - `.js` → `script` - `.css` → `stylesheet` - `.jpg/.png/.webp` → `image` - `.woff/.woff2/.ttf` → `font` - `.mp4/.webm` → `media` - `.html` (via XHR) → `xhr` - rest → `other` ### 5. Default Block-List (Konservativ) ```ts // Immer geblockt (schon via DOM-Fakes, hier doppelt abgesichert): const DEFAULT_BLOCKED: ResourceType[] = [image, font, media, stylesheet]; // User kann erweitern: new Page({ blockResourceTypes: [...DEFAULT_BLOCKED, script] }); // → Blockiert ALLE Scripts, auch GA4/Pixel (aber bricht Seite!) // Selective blocking via Callback: new Page({ shouldBlockRequest: (req) => req.type === script && (req.url.includes(google-analytics) || req.url.includes(facebook.net)) }); ``` ## API-Änderungen ### RequestManager (neu) ```ts class RequestManager { /** Check ob ein Request geblockt werden soll */ shouldBlock(url: string, type: ResourceType, method: string): boolean {} /** Block-Counter */ get blockedCount(): number {} get metrics(): { total: number; blocked: number; byType: Record<string, number> } {} } ``` ### Page (neu) ```ts class Page { /** Anzahl geblockter Requests */ get blockedRequests(): number {} /** Detaillierte Metrics */ get networkMetrics(): { total: number; blocked: number; byType: Record<string, number> } {} } ``` ## Akzeptanzkriterien - [x] DONE: Media-Fakes (images/css/fonts/video) blockieren bereits Netzwerk - [ ] `blockResourceTypes` in PageOptions (Default: `[image, font, media, stylesheet]`) - [ ] `shouldBlockRequest(req) → boolean` Callback (höchste Priorität) - [ ] RequestManager: `shouldBlock(url, type, method) → boolean` - [ ] RequestManager: `blockedCount`, `metrics` Property - [ ] ScriptLoader.fetchContent → PageNetworkManager.request() (KEIN direktes `fetch()`) - [ ] ScriptLoader kriegt `network` Reference via Constructor oder Setter - [ ] Transport-Typ `script` für Script-Requests - [ ] Blockierte Requests → `new Response(, { status: 200 })` ohne Netzwerk - [ ] Blocked Counter wird pro Navigation resettet - [ ] Default blockList blockiert Images/Fonts/Media/CSS (redundant zu media.ts, aber Absicherung) - [ ] Scripts sind NICHT im Default blockList - [ ] `page.metrics().blockedRequests` zählt korrekt - [ ] Cross-Origin Requests werden auch geblockt - [ ] Leeres Array = keine Blockierung ## Betroffene Dateien | Datei | Änderung | Aufwand | |---|---|---| | `src/network/request-manager.ts` | `shouldBlock()`, `blockResourceTypes`, `shouldBlockRequest` callback, `metrics`, `blockedCount` | ~60 Zeilen | | `src/network/network-manager.ts` | `PageNetworkManager.request()` ruft `shouldBlock()` auf | ~15 Zeilen | | `src/js/script-loader.ts` | `network` Reference, `fetchContent()` via `this.network.request()` | ~20 Zeilen | | `src/pages/page.ts` | `PageOptions.blockResourceTypes`, `shouldBlockRequest`, `networkMetrics()`, `blockedRequests` getter | ~25 Zeilen | | `src/runtime-isolation.ts` | `ContextOptions.blockResourceTypes`, `shouldBlockRequest` durchreichen | ~10 Zeilen | | `tests/unit/resource-blocking.test.ts` | NEU: 20+ Tests | ~400 Zeilen | ## Performance Impact (korrigiert) | Szenario | Requests | Speedup (Script-Blocking) | |---|---|---| | amazon.de (kein Block) | ~200 Req (davon ~160 Scripts/Bilder) | 1x | | amazon.de (media.fakes) | ~40 Req (nur Scripts+HTML) | ~5x (schon erreicht!) | | amazon.de (media.fakes + tracking block) | ~30 Req (ohne GA4/GTM) | ~6x | → **Das issue ist primär Pipeline-Integration, nicht Bandbreiten-Ersparnis** — die 80% sind schon da. ## Testplan (20 Tests) | # | Test | Typ | |---|---|---| | RB01 | shouldBlock: image URL → true | Unit | | RB02 | shouldBlock: font URL → true | Unit | | RB03 | shouldBlock: media URL → true | Unit | | RB04 | shouldBlock: script URL → false (default) | Unit | | RB05 | shouldBlock: custom blockResourceTypes=[script] | Unit | | RB06 | shouldBlockRequest Callback override | Unit | | RB07 | blockedCount incrementiert bei Block | Unit | | RB08 | metrics.blocked = total blocked | Unit | | RB09 | metrics.byType gruppiert korrekt | Unit | | RB10 | Leeres blockResourceTypes = nix blockiert | Unit | | RB11 | ScriptLoader.fetchContent → PageNetworkManager (Integration) | Unit | | RB12 | Blocked Request → 200 OK | Unit | | RB13 | Blocked Request → Empty Body | Unit | | RB14 | Cross-Origin Block | Integration | | RB15 | Block-List resettet bei neuem goto() | Integration | | RB16 | page.networkMetrics() liefert korrekte Werte | Integration | | RB17 | Script NOT blocked by default | Integration | | RB18 | shouldBlockRequest erhält korrekte Parameter (url, type, method) | Unit | | RB19 | fetch() via page JS: blockResourceTypes greift auch hier | Integration | | RB20 | Mixed: image block + scripts allowed | Integration | > **Phase 1**: RequestManager + PageNetworkManager Blocking-Logik (RB01-RB10) > **Phase 2**: ScriptLoader Integration (RB11) > **Phase 3**: Page-Level + Integrationstests (RB12-RB20) ## Risks | Risk | Impact | Mitigation | |---|---|---| | ScriptLoader.fetchContent bricht bei PageNetworkManager-Fehler | Hoch | Fallback zu `fetch(url)` im Catch | | Default blockList blockiert versehentlich notwendige Scripts | Hoch | Default blockList enthält NICHT script — nur image/font/media/stylesheet | | shouldBlockRequest wirkt auch auf HTML-Navigation | Mittel | Navigation hat type=document → nicht im Default | | WebSocket wird geblockt | Mittel | WS separat behandeln (type=websocket nicht im Default) | | iframe-Kompatibilität | Mittel | type=document nicht blocken |
Artur changed title from Resource Blocking — Image/Font/Media Requests skipen (80% Bandbreite sparen) to Resource Blocking + Unified Network Pipeline — PageNetworkManager als Single Entry Point 2026-06-19 21:10:55 +00:00
Artur closed this issue 2026-06-19 21:21:07 +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#113
No description provided.