iToverDose/Software· 7 JULY 2026 · 20:04

Chrome Extension That Reads Alerts Aloud Solves Two Manifest V3 Puzzles

A developer built a browser extension to speak notifications aloud only when they match user-defined keywords—until Manifest V3 introduced two undocumented limitations that stalled the project for days.

DEV Community3 min read0 Comments

Last week, a developer tracked how often they checked their browser notifications during a coding session and found 34 glances—only four of which required immediate attention. To solve the overload, they built Serious Notification, a Chrome extension that reads browser alerts aloud, but only when they include specified keywords like "failed" or "urgent." What started as a weekend project quickly hit two undocumented Manifest V3 roadblocks that nearly derailed the launch.

Why Standard Content Scripts Fail at Notification Interception

The initial approach seemed straightforward: use a Chrome content script to listen for notification events, filter them against keywords, and speak the matches. The plan relied on intercepting calls to the browser’s Notification API before websites processed them.

Unfortunately, Manifest V3’s default content scripts run in an isolated JavaScript environment, separate from the page’s main context. While they can read the DOM, they cannot access window.Notification—the method websites use to create notifications. By the time the script runs, the page’s Notification constructor is already set, and the extension misses the event entirely.

The core issue stems from Chrome’s split-world architecture: isolated scripts share the DOM but not the JavaScript runtime of the page. Your script can observe DOM changes but cannot override or intercept native browser APIs executed in the main world.

The Main World Workaround—and Its Hidden Cost

Manifest V3 introduced the option to run content scripts in the MAIN world by setting "world": "MAIN" in the manifest. This places the script directly in the page’s JavaScript context, allowing it to override window.Notification before any website code runs.

{
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["interceptor.js"],
      "world": "MAIN",
      "run_at": "document_start"
    }
  ]
}

With this setup, the extension can override the native Notification constructor:

const OriginalNotification = window.Notification;

window.Notification = function(title, options = {}) {
  window.dispatchEvent(
    new CustomEvent('__sn_notification__', {
      detail: { title, body: options.body || '' }
    })
  );
  return new OriginalNotification(title, options);
};

However, MAIN world scripts come with a critical limitation: they cannot access most Chrome extension APIs, including chrome.storage, speechSynthesis, or any permission-based functionality. This means the extension must split its logic into two scripts.

Bridging Isolated and Main Worlds with a Custom Event

The solution involves two scripts working in tandem:

  • interceptor.js runs in the MAIN world, overrides window.Notification, and fires a custom DOM event whenever a notification appears.
  • content.js runs in the ISOLATED world, listens for this event, and uses chrome.storage to check keywords and speechSynthesis to read alerts aloud.
window.addEventListener('__sn_notification__', async (e) => {
  const { title, body } = e.detail;
  const data = await chrome.storage.local.get(['enabled', 'keywords', 'speed']);

  if (!data.enabled) return;

  const keywords = data.keywords || [];
  const text = `${title} ${body}`.toLowerCase();
  const match = keywords.some(kw => text.includes(kw.toLowerCase()));

  if (match || keywords.length === 0) {
    const utterance = new SpeechSynthesisUtterance(`${title}. ${body}`);
    utterance.rate = data.speed || 1.0;
    speechSynthesis.speak(utterance);
  }
});

This architecture ensures the extension intercepts notifications in real time while maintaining full access to user settings and speech capabilities.

Avoiding the "Read All Your Data" Permission Warning

Initially, the extension declared <all_urls> as a required host permission in manifest.json, triggering Chrome’s most severe install warning: "Read and change all your data on all websites." Analytics revealed a 33% uninstall rate at this stage—likely users abandoning the extension before experiencing its core function.

The fix involved moving host permissions to the optional host permissions list in Manifest V3:

{
  "permissions": ["storage", "scripting"],
  "optional_host_permissions": ["<all_urls>"]
}

After installation, the extension dynamically registers content scripts only when the user explicitly grants permission via a popup button:

chrome.permissions.request({
  origins: ['<all_urls>']
});

This shifts the scary prompt from the initial install screen to an in-app action, reducing drop-offs and improving user trust.

Handling Silent Failures After Updates

A subtle but critical edge case arises when updating an extension that previously required broad host access. If a required permission becomes optional, Chrome silently revokes it for existing users during updates, causing the extension to stop working without explanation.

To prevent this, developers must proactively check and re-request permissions after updates, ensuring users retain functionality without disruption.

A Model for Future Manifest V3 Extensions

Serious Notification demonstrates how to navigate Manifest V3’s architectural constraints while maintaining functionality and user trust. By splitting logic across MAIN and ISOLATED worlds and using optional permissions, developers can build powerful extensions without sacrificing performance or security. As Chrome continues refining Manifest V3, such workarounds may become standard practice for advanced browser automation tools.

AI summary

Bir Chrome uzantısıyla tarayıcı bildirimlerini belirlediğiniz anahtar kelimelerle filtreleyerek yalnızca önemlilerinin sesli okunmasını sağladım. Fark ettim ki Manifest V3 mimarisindeki iki kritik kısıtlama, projeyi hayata geçirmeyi zorlaştırıyordu. Detaylar burada.

Comments

00
LEAVE A COMMENT
ID #4TBW5Z

0 / 1200 CHARACTERS

Human check

6 + 9 = ?

Will appear after editor review

Moderation · Spam protection active

No approved comments yet. Be first.