aboutsummaryrefslogtreecommitdiff
path: root/extensions
diff options
context:
space:
mode:
authorTianren Dong <tianrendong.dtr@gmail.com>2026-05-13 02:23:52 -0400
committerTianren Dong <tianrendong.dtr@gmail.com>2026-05-13 02:23:52 -0400
commitb319c82a75e9a6488a6454790d1436f74a655e7c (patch)
treec510e62d80b0d2484f0edeba02524624b31f2237 /extensions
parent36c2f7e3463eaa7fecb3ceba537813c089f52b3a (diff)
pi-chrome 0.14.1: harder attachDebugger retry + trusted.debug diagnostic
attachDebugger now: - Calls chrome.debugger.getTargets() first and force-detaches any stale CDP session this extension already owns on the target tab before attaching. Stale sessions (left over from SW reloads or sibling detach failures) often surface on the next attach as 'Cannot access a chrome-extension:// URL of different extension'. - Retries up to two more times (180ms + 500ms) instead of once. The 500ms back-off lets Chrome's content-script registration drain on fresh navigations. - Records every attach event (attached, stale-target-found, attach- failed, retry-failed) in a 20-entry ring buffer for inspection. New diagnostic action 'trusted.debug' returns extension version, trusted mode, attached tabs, the inspected tab's URL/status, current chrome.debugger.getTargets() output, and the recent attach-event log. Use to investigate when a specific URL refuses to attach.
Diffstat (limited to 'extensions')
-rw-r--r--extensions/chrome-profile-bridge/browser-extension/manifest.json2
-rw-r--r--extensions/chrome-profile-bridge/browser-extension/service_worker.js78
2 files changed, 68 insertions, 12 deletions
diff --git a/extensions/chrome-profile-bridge/browser-extension/manifest.json b/extensions/chrome-profile-bridge/browser-extension/manifest.json
index 0565eeb..aa7bfae 100644
--- a/extensions/chrome-profile-bridge/browser-extension/manifest.json
+++ b/extensions/chrome-profile-bridge/browser-extension/manifest.json
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Pi Chrome Connector",
- "version": "0.14.0",
+ "version": "0.14.1",
"description": "Lets Pi control tabs in Chrome via a local connector at 127.0.0.1.",
"permissions": ["tabs", "scripting", "storage", "activeTab", "alarms", "webNavigation", "debugger"],
"host_permissions": ["<all_urls>", "http://127.0.0.1:17318/*"],
diff --git a/extensions/chrome-profile-bridge/browser-extension/service_worker.js b/extensions/chrome-profile-bridge/browser-extension/service_worker.js
index b36289b..d256f1c 100644
--- a/extensions/chrome-profile-bridge/browser-extension/service_worker.js
+++ b/extensions/chrome-profile-bridge/browser-extension/service_worker.js
@@ -60,6 +60,13 @@ async function maybeUpgradeToTrusted(kind, params, syntheticResult, trustedFn) {
}
}
+// Last few attach failures, kept for /chrome doctor + trusted.debug diagnostics.
+const attachDebugLog = [];
+function recordAttachEvent(entry) {
+ attachDebugLog.push({ ...entry, t: Date.now() });
+ if (attachDebugLog.length > 20) attachDebugLog.shift();
+}
+
async function attachDebugger(tabId) {
if (!chrome.debugger) throw new Error("chrome.debugger API unavailable; reload the extension to grant the new permission");
if (attachedTabs.has(tabId)) {
@@ -67,28 +74,75 @@ async function attachDebugger(tabId) {
entry.detachAt = Date.now() + TRUSTED_IDLE_DETACH_MS;
return entry;
}
+ // Before each attach, force-detach any stale CDP target this extension owns on the tab.
+ // Chrome sometimes keeps a half-dead session around (extension reload mid-attach, etc.) and
+ // surfaces it as "Cannot access a chrome-extension://" on the next attach attempt.
try {
- await chrome.debugger.attach({ tabId }, CDP_VERSION);
- } catch (error) {
- // Chrome occasionally rejects attach with "Cannot access a chrome-extension:// URL of
- // different extension" right after a navigation, even when the target tab's URL is a
- // normal page. Wait a tick, verify the tab is on a non-privileged URL, and retry once.
- const msg = String(error?.message || error);
+ const targets = await new Promise((resolve) => chrome.debugger.getTargets((t) => resolve(t || [])));
+ for (const tgt of targets) {
+ if (tgt.tabId === tabId && tgt.attached) {
+ recordAttachEvent({ kind: "stale-target-found", tabId, target: { id: tgt.id, type: tgt.type, url: tgt.url, extensionId: tgt.extensionId } });
+ try { await chrome.debugger.detach({ tabId }); } catch {}
+ await sleep(80);
+ break;
+ }
+ }
+ } catch {}
+ const attemptAttach = async () => {
+ try {
+ await chrome.debugger.attach({ tabId }, CDP_VERSION);
+ return null;
+ } catch (error) {
+ return error;
+ }
+ };
+ let err = await attemptAttach();
+ if (err) {
+ const msg = String(err?.message || err);
const transient = /Cannot access a chrome-extension|Cannot access contents of|No tab with id|Debugger is not attached|Another debugger|Target closed/i.test(msg);
- if (!transient) throw error;
+ const tabSnapshot = await chrome.tabs.get(tabId).catch(() => null);
+ recordAttachEvent({ kind: "attach-failed", tabId, message: msg, tabUrl: tabSnapshot?.url, transient });
+ if (!transient) throw err;
+ if (!tabSnapshot || (tabSnapshot.url || "").startsWith("chrome://") || (tabSnapshot.url || "").startsWith("chrome-extension://")) {
+ throw new Error(`Chrome can't attach the debugger to this tab (${tabSnapshot?.url ?? "unknown"}). Open a normal http(s) tab and try again.`);
+ }
await sleep(180);
- const tab = await chrome.tabs.get(tabId).catch(() => null);
- if (!tab || (tab.url || "").startsWith("chrome://") || (tab.url || "").startsWith("chrome-extension://")) {
- throw new Error(`Chrome can't attach the debugger to this tab (${tab?.url ?? "unknown"}). Open a normal http(s) tab and try again.`);
+ err = await attemptAttach();
+ if (err) {
+ recordAttachEvent({ kind: "attach-retry-failed", tabId, message: String(err.message || err), tabUrl: tabSnapshot?.url });
+ // One more try after a longer settle. Some Chrome builds need ~500ms after a navigation
+ // for content-script registration on the tab to drain before chrome.debugger.attach
+ // will accept the target.
+ await sleep(500);
+ err = await attemptAttach();
+ if (err) {
+ recordAttachEvent({ kind: "attach-retry2-failed", tabId, message: String(err.message || err), tabUrl: tabSnapshot?.url });
+ throw err;
+ }
}
- await chrome.debugger.attach({ tabId }, CDP_VERSION);
}
+ recordAttachEvent({ kind: "attached", tabId });
// Seed pointer in a plausible "just left the address bar" location.
const entry = { detachAt: Date.now() + TRUSTED_IDLE_DETACH_MS, pointer: { x: 120 + Math.random() * 200, y: 80 + Math.random() * 120 } };
attachedTabs.set(tabId, entry);
return entry;
}
+async function trustedDebug(params) {
+ const tab = params?.targetId ? await chrome.tabs.get(Number(params.targetId)).catch(() => null) : null;
+ let targets = [];
+ try { targets = await new Promise((resolve) => chrome.debugger.getTargets((t) => resolve(t || []))); } catch {}
+ return {
+ extensionVersion: chrome.runtime.getManifest().version,
+ extensionId: chrome.runtime.id,
+ trustedMode: TRUSTED_MODE,
+ attachedTabs: Array.from(attachedTabs.keys()),
+ requestedTab: tab ? { id: tab.id, url: tab.url, status: tab.status, title: tab.title } : null,
+ cdpTargets: targets,
+ recentAttachEvents: attachDebugLog.slice(),
+ };
+}
+
async function detachDebugger(tabId) {
if (!attachedTabs.has(tabId)) return;
attachedTabs.delete(tabId);
@@ -639,6 +693,8 @@ async function dispatch(action, params) {
return setTrustedMode(params.mode);
case "trusted.status":
return trustedStatus();
+ case "trusted.debug":
+ return trustedDebug(params);
case "page.console.list":
return executeInTab(params, listConsoleMessages, [params.clear === true]);
case "page.network.list":