fix(extension): detect browser error pages earlier
Testing / test (push) Successful in 26s
Testing / remote-protocol-compat (0.9.3) (push) Successful in 27s
Testing / remote-protocol-compat (0.9.5) (push) Successful in 20s
Package Extension / package-extension (push) Successful in 28s
Build & Publish Package / publish (push) Successful in 31s

- Add shared browser error URL detection for Chrome, Edge, Brave, and Firefox-style about:error pages.
- Short-circuit read-only DOM and HTML commands with safe fallbacks when tabs are already on browser error pages.
- Fail navigation waits, DOM waits, polling, and URL watches with clearer error-page messages.
- Bump package and extension version to 0.9.8 and extend regression coverage for cross-browser error-page handling.
This commit is contained in:
2026-05-14 13:54:21 +02:00
parent f79ff0e3c2
commit eaa1469143
8 changed files with 61 additions and 15 deletions
+13 -2
View File
@@ -1,5 +1,5 @@
// @ts-nocheck
import { executeScript, getActiveTab, isErrorPageScriptError, isScriptableUrl } from '../core';
import { executeScript, getActiveTab, isBrowserErrorUrl, isErrorPageScriptError, isScriptableUrl } from '../core';
import { contentDispatch } from './injected';
function fallbackForErrorPageDomOp(funcName, tab) {
@@ -31,6 +31,10 @@ function fallbackForErrorPageDomOp(funcName, tab) {
export async function domOp(funcName, args = {}) {
const tab = args?.tabId ? await chrome.tabs.get(args.tabId) : await getActiveTab();
const tabUrl = tab.url || tab.pendingUrl || "";
if (isBrowserErrorUrl(tabUrl)) {
const fallback = fallbackForErrorPageDomOp(funcName, tab);
if (fallback !== undefined) return fallback;
}
if (!isScriptableUrl(tabUrl)) {
throw new Error(`Cannot run DOM commands on ${tabUrl} — navigate to a regular web page first`);
}
@@ -53,7 +57,7 @@ export async function domOp(funcName, args = {}) {
export async function domEval({ code, tabId } = {}) {
const tab = tabId ? await chrome.tabs.get(tabId) : await getActiveTab();
const tabUrl = tab.url || tab.pendingUrl || "";
if (!isScriptableUrl(tabUrl)) {
if (!isScriptableUrl(tabUrl) || isBrowserErrorUrl(tabUrl)) {
throw new Error(`Cannot run DOM commands on ${tabUrl} — navigate to a regular web page first`);
}
const results = await executeScript({
@@ -68,6 +72,10 @@ export async function domEval({ code, tabId } = {}) {
export async function domWaitFor({ selector, timeout = 10000, visible = false, hidden = false, tabId } = {}) {
const tab = tabId ? await chrome.tabs.get(tabId) : await getActiveTab();
const tabUrl = tab.url || tab.pendingUrl || "";
if (isBrowserErrorUrl(tabUrl)) {
if (hidden) return { selector, found: false };
throw new Error(`Cannot wait for DOM on browser error page ${tabUrl}`);
}
if (!isScriptableUrl(tabUrl)) {
throw new Error(`Cannot run DOM commands on ${tabUrl} — navigate to a regular web page first`);
}
@@ -101,6 +109,9 @@ export async function domWaitFor({ selector, timeout = 10000, visible = false, h
export async function domPoll({ selector, pattern, attr, timeout = 30000, interval = 500, tabId } = {}) {
const tab = tabId ? await chrome.tabs.get(tabId) : await getActiveTab();
const tabUrl = tab.url || tab.pendingUrl || "";
if (isBrowserErrorUrl(tabUrl)) {
throw new Error(`Cannot poll DOM on browser error page ${tabUrl}`);
}
if (!isScriptableUrl(tabUrl)) {
throw new Error(`Cannot run DOM commands on ${tabUrl} — navigate to a regular web page first`);
}
+5 -1
View File
@@ -1,5 +1,5 @@
// @ts-nocheck
import { getActiveTab, getAliases, resolveGroupId, tabInfo } from '../core';
import { getActiveTab, getAliases, isBrowserErrorUrl, resolveGroupId, tabInfo } from '../core';
export async function navOpen({ url, background, window: windowName, windowId: explicitWindowId, group: groupNameOrId }) {
let windowId;
if (explicitWindowId != null) {
@@ -77,6 +77,10 @@ export async function navWait({ tabId, timeout = 30000, readyState = "complete"
const interval = 200;
while (Date.now() < deadline) {
const t = await chrome.tabs.get(tab.id);
const currentUrl = t.url || t.pendingUrl || "";
if (isBrowserErrorUrl(currentUrl)) {
throw new Error(`Tab ${tab.id} is showing an error page while waiting for load (${currentUrl})`);
}
if (readyState === "complete" ? t.status === "complete" : t.status !== "loading") {
return tabInfo(t);
}
+10 -5
View File
@@ -1,5 +1,5 @@
// @ts-nocheck
import { executeScript, getActiveTab, getAliases, isScriptableUrl, resolveTabForDirectAction, tabInfo } from '../core';
import { executeScript, getActiveTab, getAliases, isBrowserErrorUrl, isErrorPageScriptError, isScriptableUrl, resolveTabForDirectAction, tabInfo } from '../core';
export async function tabsList() {
const windows = await chrome.windows.getAll({ populate: true });
const aliases = await getAliases();
@@ -102,8 +102,12 @@ export async function tabsQuery({ search }) {
export async function tabsHtml({ tabId }) {
for (let i = 0; i < 3; i++) {
const tab = tabId ? await chrome.tabs.get(tabId) : await getActiveTab();
if (!isScriptableUrl(tab.url || tab.pendingUrl || "")) {
throw new Error(`Cannot get HTML of ${tab.url || tab.pendingUrl} — navigate to a regular web page first`);
const tabUrl = tab.url || tab.pendingUrl || "";
if (isBrowserErrorUrl(tabUrl)) {
return "";
}
if (!isScriptableUrl(tabUrl)) {
throw new Error(`Cannot get HTML of ${tabUrl} — navigate to a regular web page first`);
}
try {
const results = await executeScript({
@@ -112,7 +116,8 @@ export async function tabsHtml({ tabId }) {
});
return results[0]?.result || "";
} catch (e) {
const transient = e.message && (e.message.includes("Frame with ID") || e.message.includes("No tab with id")) && !e.message.includes("error page");
if (isErrorPageScriptError(e)) return "";
const transient = e.message && (e.message.includes("Frame with ID") || e.message.includes("No tab with id"));
if (i < 2 && transient) {
await new Promise(r => setTimeout(r, 300));
continue;
@@ -205,7 +210,7 @@ export async function tabsWatchUrl({ pattern, timeout = 30000, tabId } = {}) {
lastUrl = t.url || t.pendingUrl || "";
lastStatus = t.status || "unknown";
if (matches(t.pendingUrl || "") || matches(t.url || "")) return tabInfo(t);
if ((t.url || "").startsWith("chrome-error://")) {
if (isBrowserErrorUrl(t.url || "")) {
throw new Error(`Tab ${tab.id} is showing an error page while waiting for URL to match '${pattern}'`);
}
await new Promise(r => setTimeout(r, 200));
+19 -2
View File
@@ -5,9 +5,26 @@ export async function getProfileAlias() {
return profileAlias || "default";
}
export function isBrowserErrorUrl(url) {
const value = String(url || "").toLowerCase();
return value.startsWith("chrome-error://") ||
value.startsWith("edge-error://") ||
value.startsWith("brave-error://") ||
value.startsWith("about:neterror") ||
value.startsWith("about:certerror") ||
value.startsWith("about:blocked") ||
value.startsWith("about:tabcrashed");
}
export function isErrorPageScriptError(error) {
const message = String(error?.message || error || "");
return message.includes("error page") || message.includes("chrome-error://chromewebdata");
const message = String(error?.message || error || "").toLowerCase();
return message.includes("error page") ||
message.includes("chrome-error://") ||
message.includes("edge-error://") ||
message.includes("brave-error://") ||
message.includes("about:neterror") ||
message.includes("about:certerror") ||
message.includes("about:tabcrashed");
}
export function isTransientScriptError(error) {