# FluentRead Browser Extension Architecture: Content Script and Background Script Design

> Explore the FluentRead browser extension architecture. Discover how content and background scripts manage UI, global state, and translation services for a seamless reading experience. Learn about its design.

- Repository: [ThinkStu/fluentread](https://github.com/bistutu/fluentread)
- Tags: architecture
- Published: 2026-02-26

---

**FluentRead uses a two-script browser extension architecture where the content script handles page-level UI and user interactions while the background script manages global state, context menus, and translation service routing.**

FluentRead is an open-source translation browser extension built with the **WXT** framework, enabling modern TypeScript and Vue development while compiling to standard WebExtension files. The browser extension architecture centers on a clean separation between page-specific operations and extension-wide functionality, implemented through two primary scripts in the `entrypoints/` directory.

## Overview of the FluentRead Browser Extension Architecture

The architecture follows the standard WebExtension pattern with two distinct execution contexts:

| Script | Execution Context | Primary Responsibilities |
|--------|------------------|-------------------------|
| **Content Script** | Runs in every web page | UI injection, gesture handling, translation requests |
| **Background Script** | Runs once per extension instance | Context menus, per-tab state, service routing |

Both scripts are defined using WXT helper functions that handle packaging and permissions. The content script uses `defineContentScript()` in [`entrypoints/content.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/content.ts), while the background script uses `defineBackground()` in [`entrypoints/background.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/background.ts).

## Content Script: Page-Level UI and Interaction

The content script serves as the user-facing layer of the browser extension architecture, mounting components directly into web pages and handling all direct user interactions.

### Initialization and Configuration

Upon injection, the script waits for user configuration to load from `chrome.storage` via the utility in [`entrypoints/utils/config.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/utils/config.ts). If the extension is disabled globally, the script aborts immediately to avoid unnecessary overhead.

```typescript
// entrypoints/content.ts
if (config.disableFloatingBall !== true) {
  mountFloatingBall();
}

```

### UI Component Mounting

The script mounts three primary Vue components:

- **Floating Ball**: A draggable widget for quick translation toggles
- **Selection Translator**: A panel that appears when text is selected
- **Status Indicator**: Optional component showing translation progress

These components are defined in [`entrypoints/utils/floatingBall.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/utils/floatingBall.ts) and [`entrypoints/utils/selectionTranslator.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/utils/selectionTranslator.ts).

### Event Handling and Gestures

The content script registers multiple input listeners defined in [`entrypoints/utils/constant.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/utils/constant.ts), including:

- **Double-click** and **long-press** gestures for word translation
- **Multi-finger taps** for paragraph translation
- **Hotkey combinations** for full-page translation via `setupFloatingBallHotkey()`

A custom DOM event `fluentread-toggle-translation` decouples hotkey handling from the core translation logic, allowing the background script to trigger translations programmatically.

### Message Handling from Background

The script listens for runtime messages from the background script to perform actions outside the scope of user gestures:

```typescript
browser.runtime.onMessage.addListener((msg, sender, respond) => {
  if (msg.type === 'contextMenuTranslate') {
    if (msg.action === 'fullPage') autoTranslateEnglishPage();
    else if (msg.action === 'restore') restoreOriginalContent();
    respond({ status: 'success' });
    return true;
  }
});

```

This handler enables context menu actions to trigger full-page translation or restoration of original content.

## Background Script: Global State and Service Routing

The background script operates as the central coordinator in the browser extension architecture, managing extension-wide state and interfacing with translation APIs.

### Context Menu Creation

During initialization, the script creates a parent menu item "FluentRead" with two child items:

- **全文翻译** (Full Page Translation)
- **撤销翻译** (Restore Original)

These menus are dynamically enabled or disabled based on the per-tab translation state.

### Per-Tab Translation State

The background script maintains a `Map<number, boolean>` tracking which tabs have active translations. This state determines whether the restore option should be available and prevents duplicate translation requests.

### Translation Service Routing

When the content script or context menu initiates a translation, the background script routes the request to the appropriate service implementation:

```typescript
_service[config.service](message)
  .then(resp => resolve(resp))
  .catch(error => reject(error));

```

The `_service` registry in [`entrypoints/service/_service.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/service/_service.ts) supports multiple providers including Microsoft and DeepL. The background script also handles special cases like **input-box translation** (`inputBoxTranslation`), which calls Microsoft's API directly to bypass CORS restrictions in Firefox.

### Communicating with Content Scripts

The background script sends messages to specific tabs using `browser.tabs.sendMessage()`:

```typescript
browser.tabs.sendMessage(tab.id, {
  type: 'contextMenuTranslate',
  action: 'fullPage'
});

```

This pattern allows the background script to trigger page-specific actions without direct access to the DOM.

## Communication Flow Between Scripts

The browser extension architecture employs two primary communication mechanisms:

1. **Runtime Messaging**: Standard `browser.runtime.sendMessage()` and `browser.tabs.sendMessage()` for request/response patterns between background and content scripts.

2. **Custom DOM Events**: The `fluentread-toggle-translation` event allows the background script to signal translation toggles without tightly coupling to the content script's internal implementation.

```

User Action → Background Script → Runtime Message → Content Script → Translation Service
     ↓              ↓                      ↓                ↓
Context Menu    State Check          Message Handler    UI Update

```

## Key Files and Configuration

Supporting the core browser extension architecture are several utility modules:

- **[`entrypoints/utils/config.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/utils/config.ts)**: Manages user preferences and storage synchronization.
- **[`entrypoints/utils/constant.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/utils/constant.ts)**: Defines gesture constants like `TwoFinger` and `DoubleClick`.
- **[`entrypoints/utils/floatingBall.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/utils/floatingBall.ts)**: Implements the draggable translation toggle widget.
- **[`entrypoints/utils/selectionTranslator.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/utils/selectionTranslator.ts)**: Handles the text selection translation interface.
- **[`entrypoints/service/_service.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/service/_service.ts)**: Registry for translation provider implementations.
- **[`wxt.config.ts`](https://github.com/bistutu/fluentread/blob/main/wxt.config.ts)**: Configures build permissions including `storage`, `contextMenus`, and `offscreen`.

## Summary

- FluentRead's browser extension architecture separates concerns between **content scripts** (page UI and interactions) and **background scripts** (global state and API routing).
- The **WXT framework** compiles TypeScript/Vue source files into standard WebExtension manifests.
- **Content scripts** in [`entrypoints/content.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/content.ts) mount Vue components, handle gestures, and listen for background messages.
- **Background scripts** in [`entrypoints/background.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/background.ts) manage context menus, track per-tab translation states, and route requests to translation services.
- Communication uses standard `browser.runtime` messaging and custom DOM events for loose coupling.

## Frequently Asked Questions

### How does the content script communicate with the background script in FluentRead?

The content script uses `browser.runtime.sendMessage()` to send translation requests and configuration queries to the background script. Conversely, it listens for incoming messages via `browser.runtime.onMessage.addListener()` to handle context menu commands like full-page translation or cache clearing. This standard WebExtension messaging API ensures secure cross-context communication.

### What is the role of the background script in FluentRead's browser extension architecture?

The background script acts as the central coordinator that persists beyond individual page lifecycles. It creates and manages context menu items, maintains a `Map` of per-tab translation states to track which pages are currently translated, and routes translation requests to the appropriate service provider (Microsoft, DeepL, etc.) based on user configuration stored in `chrome.storage`.

### How does FluentRead handle user gestures like double-click or long-press translation?

The content script registers event listeners for various input gestures defined in [`entrypoints/utils/constant.ts`](https://github.com/bistutu/fluentread/blob/main/entrypoints/utils/constant.ts), including `DoubleClick`, `LongPress`, and multi-finger taps. When a gesture matches the user's configured trigger, the content script invokes `handleTranslation()` to capture the selected text and send a translation request to the background script, which then returns the translated result for display in the selection translator panel.

### Why does FluentRead use the WXT framework for its browser extension architecture?

FluentRead uses WXT to write modern TypeScript and Vue code while automatically compiling to the manifest and file structure required by WebExtension APIs. WXT provides helper functions like `defineContentScript()` and `defineBackground()` that handle packaging, permissions management, and cross-browser compatibility, allowing the developers to focus on the translation logic rather than extension boilerplate.