tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

external_components_architecture.md (5949B)


External Components Architecture

Internal documentation for the New Tab team on the External Components system implementation.

Overview

The External Components system provides a pluggable architecture for embedding custom web components from other Firefox features into about:newtab and about:home. This document describes the internal architecture, data flow, and implementation details.

System Components

1. AboutNewTabComponentRegistry

Location: browser/components/newtab/AboutNewTabComponents.sys.mjs

The registry is the central coordinator for external components. It:

Since the train-hopping ExternalComponentsFeed.sys.mjs talks to it, care must be given to ensure train-hop compatibility if either changes.

Validation Rules for Registrants

2. ExternalComponentsFeed

Location: browser/extensions/newtab/lib/ExternalComponentsFeed.sys.mjs

The feed connects the registry to the Redux store and manages component data distribution.

The feed instantiates and has responsibility over the AboutNewTabComponentRegistry instance.

This feed lives within browser/extensions/newtab, and will train-hop - however, it depends on AboutNewTabComponents.sys.mjs, which does not train-hop. Care must be given to ensure train-hop compatibility if either changes.

Responsibilities

Data Flow

INIT action
    ↓
ExternalComponentsFeed.onAction()
    ↓
refreshComponents()
    ↓
AboutNewTabComponentRegistry instance.values()
    ↓
ac.BroadcastToContent(REFRESH_EXTERNAL_COMPONENTS, [...components])
    ↓
Redux Store (ExternalComponents state)

3. ExternalComponentWrapper

Location: browser/extensions/newtab/content-src/components/ExternalComponentWrapper/ExternalComponentWrapper.jsx

A React component that loads and renders external custom elements.

Component Lifecycle

<ExternalComponentWrapper type="SEARCH" className="search-wrapper" />

Mount:

  1. Responds to registry UPDATED_EVENT to refresh component data
  2. Responds to registry UPDATED_EVENT to refresh component data
  3. Responds to registry UPDATED_EVENT to refresh component data
  4. Responds to registry UPDATED_EVENT to refresh component data
  5. Responds to registry UPDATED_EVENT to refresh component data
  6. Responds to registry UPDATED_EVENT to refresh component data
  7. Responds to registry UPDATED_EVENT to refresh component data

Unmount:

  1. Responds to registry UPDATED_EVENT to refresh component data
  2. Responds to registry UPDATED_EVENT to refresh component data

Key Implementation Details

Complete Data Flow

1. Feature registers with category manager

2. AboutNewTabComponentRegistry observes category change

3. Registry emits UPDATED_EVENT

4. On ActivityStream INIT:
   ExternalComponentsFeed.onAction(INIT)
     → refreshComponents()
     → dispatch(BroadcastToContent(REFRESH_EXTERNAL_COMPONENTS))

5. Redux reducer updates state.ExternalComponents

6. ExternalComponentWrappers do the work of mapping configurations to hook
   points within the DOM.
   <ExternalComponentWrapper type="SEARCH" />
     → connect(state => ({ ExternalComponents: state.ExternalComponents }))

7. Component loads and renders at the ExternalComponentWrapper hook point:
   useEffect → import(componentURL) → createElement(tagName) → appendChild()

Adding New Features

For the New Tab Team

When adding support for a new component placement:

  1. Prevents duplicate element creation on re-renders
  2. Prevents duplicate element creation on re-renders
  3. Prevents duplicate element creation on re-renders
  4. Prevents duplicate element creation on re-renders

Example:

<div className="newtab-search-section">
  <ExternalComponentWrapper
    type="SEARCH"
    className="search-handoff-wrapper"
  />
</div>

Error Handling

The system is designed to be resilient:

All errors are logged to the browser console with descriptive messages.

Future Improvements

Potential areas for enhancement:

Debugging

Logging

Enable verbose logging:

browser.newtabpage.activity-stream.externalComponents.log=true