alyssaxuu/screenity · error · Error
content script injection failed for tab ${tabId}
Error message
content script injection failed for tab ${tabId} What it means
sendMessageEnsuringContentScript first checks whether the extension content script is present in the target tab and, if not, injects it via injectContentScriptIntoTab. This error is thrown when the injection itself returns a falsy result, meaning the content script could not be established in the tab, so sending the message would hang or fail.
Source
Thrown at src/pages/Background/utils/executeScripts.js:64
if (typeof tabId !== "number") return false;
const contentScripts = chrome.runtime.getManifest().content_scripts || [];
const files = contentScripts.flatMap((cs) => cs.js || []);
if (files.length === 0) return false;
try {
await chrome.scripting.executeScript({ target: { tabId }, files });
return true;
} catch {
return false;
}
};
// Pings first: a blind send to a loading tab hangs instead of erroring, which
// is what made the popup take minutes. A resend would toggle the popup twice.
export const sendMessageEnsuringContentScript = async (tabId, message) => {
const { sendMessageTab } = await import("../tabManagement");
if (!(await hasContentScript(tabId))) {
if (!(await injectContentScriptIntoTab(tabId))) {
throw new Error(`content script injection failed for tab ${tabId}`);
}
}
return sendMessageTab(tabId, message);
};
// Recently-used first. Sinks collapsed-group tabs without needing the
// tabGroups permission to identify them.
const injectionPriority = (tab, focusedWindowId) => {
if (tab.active) return 0;
if (focusedWindowId != null && tab.windowId === focusedWindowId) return 1;
return 2;
};
// Skips the ~1MB re-parse when the script is already there. Only absence
// reads "Receiving end does not exist", so any other reply means it is.
// A timeout counts as absent. Injection is deduped by
// window.__screenityContentBootstrapped, so guessing wrong costs one parse.
const hasContentScript = async (tabId, timeoutMs = PING_TIMEOUT_MS) => {View on GitHub (pinned to 512606387b)
Solutions
- Check the page URL before calling: skip restricted schemes (chrome://, edge://, chrome-extension://, about:) and surface a friendly 'this page is not supported' message instead of throwing.
- Verify manifest host_permissions / optional_host_permissions cover the target origin and that the 'scripting' permission is granted.
- Check tab status in the callback: if the tab is 'loading' or 'unloaded'/'discarded', wait for tabs.onUpdated status 'complete' or reload the tab, then retry once.
- Confirm the content script file path passed to chrome.scripting.executeScript is correct and the file is web-accessible/registered.
- Log the underlying chrome.runtime.lastError inside injectContentScriptIntoTab to see the real injection failure reason.
Example fix
// before
const ok = await injectContentScriptIntoTab(tabId);
if (!ok) {
throw new Error(`content script injection failed for tab ${tabId}`);
}
// after
const tab = await chrome.tabs.get(tabId);
if (!/^https?:/.test(tab.url ?? "")) {
throw new Error(`cannot inject into restricted page: ${tab.url}`);
}
await waitForTabComplete(tabId);
const ok = await injectContentScriptIntoTab(tabId);
if (!ok) {
throw new Error(`content script injection failed for tab ${tabId}`);
} Defensive patterns
Strategy: try-catch
Validate before calling
const tab = await chrome.tabs.get(tabId);
const injectable = /^https?:/.test(tab.url ?? "") && tab.status !== "unloaded";
if (!injectable) throw new Error(`Cannot inject content script into ${tab.url}`); Type guard
function isInjectableTab(tab) {
return typeof tab?.url === "string" && /^https?:/.test(tab.url);
} Try / catch
try {
await sendMessageEnsuringContentScript(tabId, message);
} catch (err) {
if (String(err.message).startsWith("content script injection failed")) {
showUnsupportedPageNotice(tabId);
} else {
throw err;
}
} Prevention
- Check tab.url against restricted schemes before messaging
- Declare host_permissions broadly or request optional host permissions for the active tab
- Wait for tabs.onUpdated status 'complete' on freshly navigated tabs
- Handle tab discard/sleep by reloading before injection
When it happens
Trigger: Calling sendMessageEnsuringContentScript(tabId, message) for a tab where hasContentScript returns false and injectContentScriptIntoTab also fails — e.g. restricted pages (chrome://, Web Store, PDF viewer), discarded/sleeping tabs, tabs without host permissions, or the tab closing/navigating mid-injection.
Common situations: User clicks the popup while the active tab is a browser-internal page; extension updated but old content script absent and scripting permission/host grants missing; tab was put to sleep by a tab-suspender extension; MV3 service worker woke up after the tab navigated.
AI-assisted analysis of alyssaxuu/screenity@512606387b (2026-09-02).
Data as JSON: /api/errors/c233080a9d603d48.
Report an issue: GitHub.