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:
Observes the
browser-newtab-external-componentcategory for registrant modulesLoads and validates registrants and their component configurations
Maintains a deduplicated map of components keyed by type
Emits
UPDATED_EVENTwhen components are added or removedProvides access via
AboutNewTabComponentRegistry.instance()Lives under
browser/components/newtab, and therefore, does not train-hop. Since the train-hoppingExternalComponentsFeed.sys.mjstalks to it, care must be given to ensure train-hop compatibility if either changes.
Validation Rules for Registrants
Registrants must extend
BaseAboutNewTabComponentRegistrantComponent configurations must have
type,componentURL, andtagNameDuplicate types are rejected (first registrant wins)
Invalid configurations are logged but don’t break the system
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
Initializes on
INITactionQueries the registry for all registered components
Dispatches
REFRESH_EXTERNAL_COMPONENTSto broadcast component data to content processesResponds to registry
UPDATED_EVENTto refresh component data
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:
Look up configuration by type from Redux store
If no config, log warning and return
Dynamically import the component module
Create and append localization link elements to document head
Create the custom element via
document.createElement()Apply attributes and CSS variables from configuration
Append custom element to container div
Unmount:
Remove custom element from DOM
Remove localization link elements
Key Implementation Details
Uses
useEffectwith dependency on[type, ExternalComponents.components]Uses
importModuleprop for dependency injection (enables testing)Uses refs to track custom element and l10n links
Renders error state (null) if component loading fails
Prevents duplicate element creation on re-renders
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:
Add
<ExternalComponentWrapper>to the desired location in your React componentSpecify the
typeprop matching the component typeAdd appropriate CSS for the wrapper element
Update tests to account for the new component location
Example:
<div className="newtab-search-section">
<ExternalComponentWrapper
type="SEARCH"
className="search-handoff-wrapper"
/>
</div>
Error Handling
The system is designed to be resilient:
Invalid registrants are logged but don’t crash the registry
Invalid component configurations are skipped
Component loading errors are caught and logged
Failed components render null without breaking the page
All errors are logged to the browser console with descriptive messages.
Future Improvements
Potential areas for enhancement:
Add support for component communication to the parent process via custom events and subclassable JSActor pairs
Add support for React components (not just custom elements)
Add component lifecycle hooks for more complex initialization
Add support for conditional rendering based on prefs or experiments
Add performance monitoring for component load times
Add support for component updates without full remount
Add support for opt-in train-hopping for external components
Debugging
Logging
Enable verbose logging:
browser.newtabpage.activity-stream.externalComponents.log=true