Skip to content

Recover when a page has no content script (#126) - #131

Merged
forgetso merged 1 commit into
masterfrom
fix-126-content-script-connection
Sep 21, 2026
Merged

forgetso merged 1 commit into
masterfrom
fix-126-content-script-connection

Conversation

@forgetso

Copy link
Copy Markdown
Owner

Fixes the failure reported in #126 on Meta Ads Manager.

What the reporter's screenshots show

The third screenshot is the extension's own error log:

Uncaught (in promise) Error: Could not establish connection. Receiving end does not exist.
Kontext:     assets/popup.html
Stacktrace:  popup.js:1

That is Chrome's wording for tabs.sendMessage finding nothing listening in the tab — it came from chrome.tabs.sendMessage(tab.id, message) in contentScriptCall.

Chrome only injects content scripts into pages loaded after the extension is installed, updated or reloaded, and it updates extensions silently in the background. A tab left open for days — an ads dashboard, say — loses its content script without anything visible happening. Every later attempt then rejects, the popup does nothing at all, and the rejection is unhandled.

It also explains the reporter's "In the past it worked. Since 1 year it doesnt work." Nothing about the page changed; the extension updated underneath a long-lived tab.

The fix

deliverToContentScript injects the content script and retries, rather than giving up:

situation before after
content script present works works, no injection
tab predates the extension or its last update uncaught error, popup does nothing injected, retried, works
page cannot be injected into uncaught error, spinner runs forever says "reload the page and try again"

The last row matters: formSubmitHandler shows a spinner and waits for a reply that arrives via chrome.runtime.onMessage. When the message was never delivered, no reply ever comes and the spinner span forever. It now stops and explains itself.

Errors that are not a missing receiver are re-thrown rather than hidden behind an injection attempt.

Adds one English string, pageUnreachable. Other locales fall back to English through the existing translation proxy.

Tests

10 new unit tests in src/popup/deliver.test.ts, including the exact error text from the report. The chrome test double grew the ability to simulate a tab with no listener, a tab that cannot be injected into, and a tab that navigates away between injecting and retrying.

624 unit tests and 28 Cypress tests pass, with typecheck, lint and format clean.

What this does not fix

The reporter is replacing a two-line term containing emoji inside Meta's rich text editor. Even with delivery fixed, that specific replacement will still fail, and it is a separate problem:

textarea, value is one string          count=1  replaced correctly
contenteditable, each line its own div count=0  replaced nothing

For a contenteditable, getValue returns innerHTML in text mode, so a term containing \n is compared against markup where the line break is </div><div> — it cannot match. Rich editors such as Meta's also keep their own document model and may revert direct DOM edits regardless.

I would rather report that precisely than guess at a fix for a page I cannot reach. Worth asking the reporter for the saved HTML that was already requested on the issue, then addressing it separately.

🤖 Generated with Claude Code

Chrome only injects content scripts into pages loaded after the extension
is installed, updated or reloaded, and Chrome updates extensions silently
in the background. A tab left open for days — a dashboard, say — quietly
loses its content script. Every later tabs.sendMessage then rejects with
"Could not establish connection. Receiving end does not exist", the popup
does nothing at all, and the rejection surfaces as an uncaught error in
the extension's log. That is exactly what the reporter of #126 saw on
Meta Ads Manager, and it explains "it worked in the past, and a year ago
it stopped".

The popup now injects the content script and retries instead of failing.
Where the page genuinely cannot be reached — chrome://, the Web Store, a
PDF viewer, a file:// URL without file access — it says so rather than
leaving the spinner running forever waiting for a reply that is not
coming.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@forgetso
forgetso merged commit a930386 into master Sep 21, 2026
5 checks passed
@forgetso forgetso linked an issue Sep 21, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inject the content script on page load

1 participant