# Read Frog Content Script Architecture: How It Handles Page Translation Across Entrypoints

> Discover how Read Frog's content script architecture manages page translation across entrypoints. Learn about its efficient coordination of observers and messaging for seamless content updates.

- Repository: [MengXi/read-frog](https://github.com/mengxi-ream/read-frog)
- Tags: architecture
- Published: 2026-03-07

---

**Read Frog centralizes page translation in a single `PageTranslationManager` instance that coordinates `IntersectionObserver` and `MutationObserver` instances to detect and translate content, while isolated content script entry points communicate via `chrome.runtime.sendMessage` to delegate translation tasks without sharing DOM references.**

Read Frog is an open-source browser extension that injects multiple content scripts into webpages to provide seamless translation capabilities. Understanding how its content script architecture handles page translation across different entrypoints reveals sophisticated patterns for managing isolated worlds in Chrome Manifest V3 extensions. The system maintains translation state through a centralized manager while distributing detection responsibilities across specialized entry points.

## Architecture Overview

Read Frog implements **three distinct content-script entry points** that serve different purposes within the same webpage:

- **`host.content`** – Contains the core translation logic in [`src/entrypoints/host.content/translation-control/page-translation.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/entrypoints/host.content/translation-control/page-translation.ts), handling page-wide translation, node-level processing, and shortcut triggers
- **`interceptor.content`** – Observes the page for dynamic media elements like video players in [`src/entrypoints/interceptor.content/index.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/entrypoints/interceptor.content/index.ts), injecting interceptor scripts that forward messages to the host
- **`guide.content`** – Manages the onboarding guide overlay through [`src/entrypoints/guide.content/index.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/entrypoints/guide.content/index.ts), remaining isolated from the translation observer graph to avoid performance overhead

All entry points share a common translation engine located under `src/utils/host/translate`, but only the host script instantiates the `PageTranslationManager`. Because Chrome Manifest V3 enforces **isolated worlds** for content scripts, entry points cannot directly share DOM references or JavaScript state. Instead, they communicate through `chrome.runtime.sendMessage` wrapped by [`src/utils/message.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/utils/message.ts), with the host script serving as the single authority for all translation operations.

## The PageTranslationManager Core

The `PageTranslationManager` class orchestrates page translation through multiple independent triggers registered in [`src/entrypoints/host.content/translation-control/page-translation.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/entrypoints/host.content/translation-control/page-translation.ts). When the host script loads, it initializes the manager and binds user interaction handlers:

- **Touch-gesture triggers** – Four-finger tap detection via `registerPageTranslationTriggers()`
- **Keyboard shortcuts** – User-configurable hotkeys bound in [`src/entrypoints/host.content/translation-control/bind-translation-shortcut.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/entrypoints/host.content/translation-control/bind-translation-shortcut.ts)
- **Configuration listeners** – Mode changes (bilingual, translation-only) handled by `handleTranslationModeChange` in [`src/entrypoints/host.content/translation-control/handle-config-change.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/entrypoints/host.content/translation-control/handle-config-change.ts)

The manager persists as a singleton throughout the page lifecycle, maintaining observer states and translation caches until explicitly stopped.

## Translation Execution Flow

### Observer Registration and Viewport Detection

When `PageTranslationManager.start()` executes, it establishes aggressive viewport monitoring to ensure translations render before content becomes visible. The manager registers two primary observation mechanisms:

1. An **`IntersectionObserver`** watches top-level paragraph elements with a **600-pixel root margin**, triggering translation logic when elements approach the viewport
2. **Multiple `MutationObserver` instances** monitor the entire document tree including shadow roots, detecting new nodes, style changes, and attribute modifications

The generous root margin ensures `translateWalkedElement` receives elements before the user scrolls to them, creating a seamless reading experience without visible translation delays.

### DOM Walking and Element Labeling

Before translation occurs, elements must be marked with a unique traversal identifier. The manager calls `walkAndLabelElement` from [`src/utils/host/dom/traversal.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/utils/host/dom/traversal.ts):

```typescript
const walkId = crypto.randomUUID()
walkAndLabelElement(container, walkId, config)

```

This function sets the `data-read-frog-walked` attribute on every element in the current traversal, creating a labeled subgraph that subsequent translation steps can identify. The UUID-based walk ID prevents stale elements from being processed multiple times if the DOM changes during translation.

### Batching and Provider API Integration

When observers detect walked elements entering the viewport, `translateWalkedElement` in [`src/utils/host/translate/core/translation-walker.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/utils/host/translate/core/translation-walker.ts) validates the element state:

- Checks for existing translation wrappers via `CONTENT_WRAPPER_CLASS` to avoid duplicate processing
- Verifies the element carries the current walk ID from `data-read-frog-walked`
- Recursively walks the DOM to distinguish block-level versus inline text fragments

Inline fragments batch together through `translateNodes` in [`src/utils/host/translate/core/translation-modes.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/utils/host/translate/core/translation-modes.ts), which implements bilingual and translation-only rendering modes. The system then delegates to provider-specific APIs under `src/utils/host/translate/api/*` to fetch and inject translations.

## Cross-Entry Point Communication

Isolated entry points interact with the translation engine through message passing rather than direct instantiation.

**The interceptor script** (`interceptor.content`) detects dynamic media players inside isolated shadow roots that the host script cannot access directly. When it discovers a new player, it posts a message to the host script, which then adds the player's root element to the manager's observation set via `observerTopLevelParagraphs` and `walkAndLabelElement`.

**The guide script** (`guide.content`) remains completely separate from the translation pipeline. It toggles UI visibility without participating in DOM observation, preventing onboarding elements from triggering unnecessary translation cycles.

This architecture ensures that only the host script manipulates the `PageTranslationManager` state, while specialized entry points focus on detection and messaging.

## State Management and Cleanup

Translation state persists until explicitly disabled. When users toggle translation off or change modes, `handleTranslationModeChange` restarts the manager with new configuration parameters.

Upon stopping, the manager executes `removeAllTranslatedWrapperNodes()` from [`src/utils/host/translate/dom/translation-cleanup.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/utils/host/translate/dom/translation-cleanup.ts), which:

- Disconnects all `IntersectionObserver` and `MutationObserver` instances
- Removes injected translation wrapper elements
- Restores the original DOM structure without translation artifacts

The following example demonstrates initializing translation from a popup UI:

```typescript
// src/entrypoints/popup/atoms/auto-translate.ts
import { PageTranslationManager } from '@/entrypoints/host.content/translation-control/page-translation'

let manager: PageTranslationManager | null = null

async function enablePageTranslation() {
  if (!manager) {
    manager = new PageTranslationManager()
    manager.registerPageTranslationTriggers()
  }
  await manager.start()
}

document.getElementById('auto-translate-toggle')?.addEventListener('click', () => {
  manager?.isActive ? manager.stop() : void enablePageTranslation()
})

```

## Summary

- **Read Frog uses three isolated content script entry points** (`host.content`, `interceptor.content`, `guide.content`) that cannot share DOM references due to Chrome Manifest V3 security boundaries
- **The `PageTranslationManager`** in [`src/entrypoints/host.content/translation-control/page-translation.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/entrypoints/host.content/translation-control/page-translation.ts) serves as the single authority for translation state, coordinating observers and provider APIs
- **Preemptive observation** via 600-pixel margin `IntersectionObserver` and shadow root-aware `MutationObserver` instances ensures translations render before content enters the viewport
- **DOM walking** with `crypto.randomUUID()` walk IDs in [`src/utils/host/dom/traversal.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/utils/host/dom/traversal.ts) creates labeled element sets that prevent duplicate processing
- **Cross-script communication** occurs exclusively through `chrome.runtime.sendMessage` wrappers in [`src/utils/message.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/utils/message.ts), with the interceptor forwarding shadow root detections to the host script
- **Complete cleanup** via `removeAllTranslatedWrapperNodes()` restores original content when translation disables

## Frequently Asked Questions

### How does Read Frog handle translation in shadow DOM elements that are isolated from the main document?

The `interceptor.content` entry point specifically monitors for dynamic media players and components rendered inside shadow roots. When it detects new shadow hosts, it posts a message to the `host.content` script via `chrome.runtime.sendMessage`. The host script then adds the shadow root to the `PageTranslationManager`'s observation set, allowing the standard `IntersectionObserver` and `MutationObserver` logic to traverse and translate content within the isolated tree.

### What prevents Read Frog from translating the same text multiple times during page mutations?

Before processing any element, the system assigns a unique walk ID using `crypto.randomUUID()` through `walkAndLabelElement` in [`src/utils/host/dom/traversal.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/utils/host/dom/traversal.ts). This ID attaches to elements via the `data-read-frog-walked` attribute. The `translateWalkedElement` function in [`src/utils/host/translate/core/translation-walker.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/utils/host/translate/core/translation-walker.ts) checks this ID against the current session and skips elements already wrapped with `CONTENT_WRAPPER_CLASS`, ensuring idempotent translation even during aggressive DOM mutations.

### Why does Read Frog use a 600-pixel root margin for its IntersectionObserver?

The 600-pixel root margin creates a buffer zone that triggers translation logic before content actually enters the viewport. According to the source code in [`src/entrypoints/host.content/translation-control/page-translation.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/entrypoints/host.content/translation-control/page-translation.ts), this preemptive observation ensures translations complete and render while content is still below the fold, preventing users from seeing untranslated text flash or load delays as they scroll through long documents.

### Can multiple content script entry points create separate PageTranslationManager instances?

No, the architecture enforces a singleton pattern where only `host.content` instantiates and owns the `PageTranslationManager`. Other entry points like `interceptor.content` and `guide.content` run in isolated JavaScript worlds and cannot access the manager directly. They must communicate via `chrome.runtime.sendMessage` as implemented in [`src/utils/message.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/utils/message.ts). This design prevents race conditions and ensures consistent translation state across all DOM mutations and user interactions.