DEV Community

Cover image for I spent a week trying to intercept Slack push notifications from a Chrome extension. Here's why it's impossible.
M.Bilal Khan
M.Bilal Khan

Posted on

I spent a week trying to intercept Slack push notifications from a Chrome extension. Here's why it's impossible.

After I published my last article about building a Chrome extension that speaks browser notifications aloud, a commenter asked a question I didn't have a good answer to.

He pointed out that a lot of web apps — Slack, Gmail, most modern tools — fire their notifications from a service worker via registration.showNotification(), not from the page's JavaScript context. My MAIN world override of window.Notification would never reach those.

He was right. And I told him I'd look into it.

I spent a week researching whether there was any way to close that gap. There isn't. But the reason why is more interesting than a simple "no."


Two ways a website can show you a notification

When a website sends you a browser notification, it can do it in one of two ways.

The first is the constructor path. The page's own JavaScript calls new Notification("You have a message") directly. This is common for in-tab alerts, real-time updates when you're actively on the site, or any notification triggered by something you just did.

The second is the push path. The browser receives a push event from the website's server, wakes up the website's service worker in the background, and the service worker calls self.registration.showNotification() from inside its own scope. This is what happens when Slack notifies you of a new message while the tab is closed or backgrounded. The page never runs. No page JavaScript ever fires.

My extension catches the first path. The MAIN world content script overrides window.Notification before any page code runs. But the service worker never touches the page's window. It has no window. It runs in a completely isolated thread, completely separate from the page, and calls showNotification on itself.

The override is never reached.


Why can't the extension reach the service worker?

This is the part that took me a week to fully accept.

Chrome extensions can inject content scripts into web pages. They can run code in the MAIN world or the ISOLATED world of a page. They can observe network requests, intercept navigations, and modify headers.

What they cannot do is inject code into a third-party website's service worker.

Service workers operate in a separate thread with no DOM, no window, and no connection to the page's JavaScript context. The Chrome Extensions API simply does not offer a mechanism to run extension code inside an arbitrary third-party service worker scope. It's a hard security boundary — and for good reasons. Allowing extensions to arbitrarily patch service workers for any website on the internet would be a significant attack surface.

So: the extension lives on the page side. The push notification lives on the service worker side. There is a wall between them.


Three approaches I investigated

I didn't accept "impossible" without looking for workarounds. Here's what I found.

Approach 1: Override ServiceWorkerRegistration.prototype.showNotification in the MAIN world

A page's JavaScript can call navigator.serviceWorker.ready.then(reg => reg.showNotification(...)). If you override ServiceWorkerRegistration.prototype.showNotification in the MAIN world, you'd intercept that pattern.

The problem: Slack's push notifications don't come from page JavaScript. They come from inside the service worker script itself, where it calls self.registration.showNotification(). The service worker runs in its own scope. The MAIN world prototype override doesn't reach there.

This approach catches one narrow pattern and misses the actual push notification path entirely.

Approach 2: chrome.debugger API with CDP

This is the most discussed workaround in the Chromium Extensions community. The chrome.debugger API gives an extension access to the Chrome DevTools Protocol. With CDP's Fetch.fulfillRequest command, you can intercept a network response — including a service worker JavaScript file — and modify it before the browser processes it. Inject your code into the SW script, and it runs inside the service worker scope.

This technically works. It is completely unusable for a consumer extension.

When you attach the debugger to any tab, Chrome displays a persistent banner at the top of the browser: "Chrome is being controlled by automated test software." This banner appears on every tab. It cannot be dismissed. It cannot be hidden by the extension. The only way to suppress it is to launch Chrome with --silent-debugger-extension-api, which is not something a real user would do.

For a Chrome Web Store extension that people install on their own machine, this approach would immediately look like malware. Ruled out.

Approach 3: declarativeNetRequest

Manifest V3's declarativeNetRequest API lets extensions block or redirect network requests. Could you redirect the Slack service worker script to a modified version?

No. declarativeNetRequest can block or redirect a request, but it cannot modify the response body. And even if it could redirect the service worker script URL, the replacement would need to be served from the same origin as the site — extensions can't serve content from slack.com.


What Firefox can do that Chrome can't

Here's where it gets interesting.

Firefox has an API called webRequest.filterResponseData(). It lets an extension intercept a network response and modify its body before the browser processes it. Applied to a service worker JavaScript file, you could inject code that runs inside the SW scope. That code could override self.registration.showNotification and intercept push notifications.

This capability exists in Firefox. It was advocated for by Giorgio Maone, the developer of NoScript, who needed exactly this kind of deep injection for a privacy tool.

Chrome removed blocking webRequest in MV3 and never added a filterResponseData equivalent. This is one of the more significant functional divergences between the two extension models post-MV3. Extension developers have raised it on the Chromium mailing lists. For now, it's a genuine gap.


What this means for Serious Notification

The extension works well for the constructor path — notifications fired from page JavaScript when a tab is active. For many apps and many use cases, that's most of their notification volume.

For pure push-via-service-worker notifications from backgrounded tabs — like Slack when you've minimised the window — the extension will miss them. I've added a Known Limitations section to the original article to reflect this honestly.

What would need to change for this to be solvable on Chrome? Some form of extension access to service worker scope — whether through a filtered response body API like Firefox's, or through a purpose-built notification interception hook in the Chrome Extensions API. Neither exists today.

If you want to see this change, the Chromium issue tracker is the place to make that case. I'm watching it.

In the meantime, if you want to try what's possible now:

🔗 https://chromewebstore.google.com/detail/hnaggblalhlbihfaegbknioadncpcged?utm_source=devto&utm_medium=content&utm_campaign=article2-sw

Top comments (0)