DEV Community

Weather Clock Dash
Weather Clock Dash

Posted on

Firefox New Tab Extension Architecture: How 6 Different Tools Use the Same Override Mechanism

Every Firefox extension that replaces the new tab uses the same fundamental mechanism: chrome_url_overrides.newtab in manifest.json. What varies is the complexity of what gets loaded.

Here is the architecture I used across the 6 new tab extensions I built.


The basic mechanism

// manifest.json
{
  "chrome_url_overrides": {
    "newtab": "newtab.html"
  }
}
Enter fullscreen mode Exit fullscreen mode

That is the entire extension-side declaration. Firefox will load newtab.html whenever the user opens a new tab.

The HTML file can be as simple as a static page or as complex as a full React application. The browser treats it like any other extension page — it has access to the browser.* WebExtensions API, but no access to the webpage content of previously open tabs.


Storage

All 6 new tab extensions store data in browser.storage.local. This is the extension-equivalent of localStorage — synchronous by default, persisted to disk, isolated per extension.

// Save
await browser.storage.local.set({ tasks: taskArray });

// Load
const { tasks } = await browser.storage.local.get('tasks');
Enter fullscreen mode Exit fullscreen mode

The benefit over localStorage: it is accessible from background scripts, content scripts, and popup pages in addition to the new tab page. All extension contexts share the same storage namespace.

The limitation: browser.storage.local is device-local. It does not sync across devices. browser.storage.sync syncs data via the user's Firefox account, but has a 100KB limit and slower read latency. For most of these extensions (task lists, habit grids, stock symbols), local storage is the right choice.


Real-time data: stocks and weather

Stock Market Dashboard and Weather Clock fetch external data. Both use the same pattern:

  1. Fetch data at load time
  2. Cache in browser.storage.local with a timestamp
  3. On subsequent loads, use the cached value if it is less than N minutes old
  4. Refetch in the background and update the display when fresh data arrives
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes

async function getStockData(symbol) {
  const { stockCache } = await browser.storage.local.get('stockCache');
  const cached = stockCache?.[symbol];
  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
    return cached.data;
  }
  const fresh = await fetchFromAPI(symbol);
  await browser.storage.local.set({
    stockCache: { ...stockCache, [symbol]: { data: fresh, timestamp: Date.now() } }
  });
  return fresh;
}
Enter fullscreen mode Exit fullscreen mode

The rendering approaches

I used vanilla JavaScript for the simpler extensions (Habit Tracker, Quick Calculator, Daily Planner) and a lightweight reactive approach for extensions with more dynamic state (Stock Market, Weather Clock).

Avoiding a framework dependency keeps the bundle size small (under 50KB for most) and the startup time fast. A new tab opens dozens of times per day — anything that adds 200ms of initialization is noticeable.


Why no external dependencies

All 11 extensions have zero npm dependencies in production. No React, no Vue, no utility libraries. The reasons:

  1. Bundle size: dependencies add weight; a new tab page should load in under 100ms
  2. Security review: Firefox AMO reviewers check for known-vulnerable npm packages
  3. Maintenance: zero dependencies means zero dependency upgrade churn

The tradeoff is more verbose JavaScript. For extensions of this size (most are under 300 lines of logic), this is worth it.


I built 6 of these new tab extensions — Habit Tracker, Daily Planner, Stock Market, Weather Clock, Quick Calculator, Speed Dial, and News Widget. All in the series.

Top comments (0)