Multi-Layer Architecture
The extension operates across four isolated contexts, each with specific responsibilities:1. Main World (React App)
Location: Injected<script> in host pageAccess: Full DOM access, same origin as host page
Limitations: Cannot use
chrome.* APIs directly
Responsibilities:
- Render React UI in Shadow DOM
- Handle user interactions
- Manage 3D character (Babylon.js)
- Display chat messages and animations
The React app runs in the “Main World” (same JavaScript context as the host page) but renders UI inside a Shadow DOM for CSS isolation.
2. Content Script (Isolated World)
Location:extension/content/index.jsAccess: Separate JavaScript context, same DOM
File: `/home/daytona/workspace/source/extension/content/index.js:1 Responsibilities:
- Inject React app script into page
- Bridge between Main World and Background
- Manage Shadow DOM container
- Handle injection lifecycle (show/hide/cleanup)
Content scripts bridge the gap between the Main World (which can’t access
chrome.* APIs) and the Background Worker (which can).3. Background Service Worker
Location:extension/background/index.jsAccess: Full
chrome.* API access, no DOMLifecycle: Persistent (kept alive during active sessions)
File: `/home/daytona/workspace/source/extension/background/index.js:1 Responsibilities:
- Handle all AI/TTS/STT service requests
- Manage per-tab state via TabManager
- Route messages to appropriate handlers
- Control offscreen document lifecycle
- Make external API calls
4. Offscreen Document
Location:extension/offscreen/offscreen.jsAccess: AudioContext, Web Workers, full processing power
Purpose: Heavy audio processing and Kokoro TTS
File: `/home/daytona/workspace/source/extension/offscreen/offscreen.js:1 Why Offscreen?
- Service workers have limited runtime (30 seconds idle timeout)
- AudioContext not available in service workers
- Heavy processing (Kokoro TTS, lip sync) needs stable context
- Can run for minutes without interruption
- Kokoro TTS model loading and inference (1-2 minutes)
- Audio decoding with AudioContext
- VMD lip sync generation (10-20 seconds)
- BVMD animation conversion
The offscreen document only handles messages explicitly targeted to it (
message.target === 'offscreen'), allowing it to coexist with other message listeners.Message Passing System
Message Flow
Typical message flow for an AI chat request:Streaming Messages
For streaming responses (AI chat, TTS):Message Types
Defined inextension/shared/MessageTypes.js:
Shadow DOM Isolation
VAssist uses Shadow DOM to achieve zero CSS conflicts with host pages.Architecture
Why Shadow DOM?
- CSS Isolation: Host page styles can’t affect VAssist UI
- JavaScript Isolation: Host page scripts can’t interfere
- Clean Namespace: No ID/class conflicts
- Scoped Tailwind: Tailwind styles only apply inside shadow root
Canvas Portal
The 3D canvas lives outside Shadow DOM because:- Babylon.js WebGL context needs direct browser access
- Better performance without shadow boundary overhead
- Positioned absolutely, so no layout conflicts
Per-Tab State Management
Each browser tab maintains completely isolated state:Tab State Structure
Per-Tab Services
Automatic Cleanup
Service Worker Lifecycle
Keepalive Strategy
Service workers can terminate after 30 seconds of inactivity. VAssist keeps the worker alive during:- Active AI chat sessions
- TTS/STT processing
- Kokoro model loading (1-2 minutes)
- VMD generation (10-20 seconds)
Offscreen Document Lifecycle
Security Considerations
Content Security Policy
'wasm-unsafe-eval': Required for Transformers.js (Kokoro TTS)'self': Only load scripts from extension origin
Permissions
Host Permissions
Testing the Extension
Loading Unpacked Extension
1
Build the extension
2
Open Chrome Extensions page
Navigate to
chrome://extensions/ and enable “Developer mode”3
Load unpacked
Click “Load unpacked” and select the
dist/extension folder4
Test on websites
Visit any website and click the extension icon to toggle VAssist
Multi-Tab Testing
- Open VAssist in Tab A
- Start a conversation
- Open VAssist in Tab B
- Start a different conversation
- Switch between tabs - each should maintain independent state
Debugging Tips
- Check all consoles: Main page, background worker, offscreen document
- Use
Loggerprefixes:[Content],[Background],[Offscreen] - Monitor message flow: Add breakpoints in message bridges
- Test cleanup: Close tabs and verify state cleanup
Next Steps
- Contributing Guide - Start contributing
- Architecture Overview - Understand code organization
- Components Reference - Explore available APIs