From 1afafb4e205bc3ec98190f618896fd4a996b54d8 Mon Sep 17 00:00:00 2001 From: Kam Date: Fri, 25 Sep 2026 19:54:18 +0000 Subject: [PATCH 1/3] fix(extension): only look for devframe on a loopback page Opening the panel probed the inspected page for a connection file before anything checked its host, so visiting any site put requests for `/__ng-devtools/__devframe/__connection.json` and five more in that site's logs, and told it the extension is installed. The panel only ever connects to localhost or 127.0.0.1, so the result could not be used anyway. Read the origin first and stop there when it is not loopback. The probes also ran inside the page through eval(), which forced them to be synchronous XMLHttpRequests and blocked the page's main thread. Run them from the panel instead, where fetch can be awaited; the manifest already grants host access to exactly the two loopback hosts. The host check in loadPanel() stays as it was. --- extension/panel-bridge.js | 79 ++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/extension/panel-bridge.js b/extension/panel-bridge.js index b1623d1..6e4563e 100644 --- a/extension/panel-bridge.js +++ b/extension/panel-bridge.js @@ -7,43 +7,54 @@ const status = document.getElementById('status'); const tabId = chrome.devtools.inspectedWindow.tabId; const LOCAL_HOSTS = ['localhost', '127.0.0.1']; -// Try to find the devframe connection on the inspected page +// Where devframe may be mounted. +const PATHS = ['/__ng-devtools/', '/__devframe/', '/']; +const CONNECTION_FILES = ['__devframe/__connection.json', '__connection.json']; + +// Look for a devframe connection, but only on a loopback page: nothing else +// can be connected to, so nothing else is worth probing. function detectConnection() { - // Try common devframe mount paths - const paths = ['/__ng-devtools/', '/__devframe/', '/']; + chrome.devtools.inspectedWindow.eval('location.origin', (origin, error) => { + if (error || typeof origin !== 'string') { + loadPanel(null); + return; + } - chrome.devtools.inspectedWindow.eval( - `(function() { - const paths = ${JSON.stringify(paths)}; - for (const base of paths) { - try { - const xhr = new XMLHttpRequest(); - xhr.open('GET', base + '__devframe/__connection.json', false); - xhr.send(); - if (xhr.status === 200) { - return { base: base, connection: JSON.parse(xhr.responseText) }; - } - } catch(e) {} - try { - const xhr = new XMLHttpRequest(); - xhr.open('GET', base + '__connection.json', false); - xhr.send(); - if (xhr.status === 200) { - return { base: base, connection: JSON.parse(xhr.responseText) }; - } - } catch(e) {} - } - return null; - })()`, - (result, err) => { - if (result && paths.includes(result.base)) { - loadPanel(result.base); - } else { - // No live devframe found — load in standalone/static mode - loadPanel(null); + let hostname; + try { + hostname = new URL(origin).hostname; + } catch { + loadPanel(null); + return; + } + + if (!LOCAL_HOSTS.includes(hostname)) { + loadPanel(null); + return; + } + + findConnection(origin).then(loadPanel); + }); +} + +// The first mount path that answers with a connection file, or null. +async function findConnection(origin) { + for (const base of PATHS) { + for (const file of CONNECTION_FILES) { + try { + const response = await fetch(new URL(base + file, origin), { + credentials: 'omit', + cache: 'no-store', + }); + if (!response.ok) continue; + await response.json(); + return base; + } catch { + // Not mounted here; try the next one. } - }, - ); + } + } + return null; } function loadPanel(baseURL) { From d07da7bd435cebfe55eb3e8b0aa6a7969c0a2f59 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 26 Sep 2026 13:03:29 +0300 Subject: [PATCH 2/3] fix(extension): tie the probed origin to the panel and time out slow probes --- extension/panel-bridge.js | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/extension/panel-bridge.js b/extension/panel-bridge.js index 6e4563e..23e3a08 100644 --- a/extension/panel-bridge.js +++ b/extension/panel-bridge.js @@ -10,11 +10,16 @@ const LOCAL_HOSTS = ['localhost', '127.0.0.1']; // Where devframe may be mounted. const PATHS = ['/__ng-devtools/', '/__devframe/', '/']; const CONNECTION_FILES = ['__devframe/__connection.json', '__connection.json']; +const PROBE_TIMEOUT_MS = 1500; + +let detection = 0; // Look for a devframe connection, but only on a loopback page: nothing else // can be connected to, so nothing else is worth probing. function detectConnection() { + const run = ++detection; chrome.devtools.inspectedWindow.eval('location.origin', (origin, error) => { + if (run !== detection) return; if (error || typeof origin !== 'string') { loadPanel(null); return; @@ -33,7 +38,9 @@ function detectConnection() { return; } - findConnection(origin).then(loadPanel); + findConnection(origin).then((base) => { + if (run === detection) loadPanel(base, origin); + }); }); } @@ -45,6 +52,7 @@ async function findConnection(origin) { const response = await fetch(new URL(base + file, origin), { credentials: 'omit', cache: 'no-store', + signal: AbortSignal.timeout(PROBE_TIMEOUT_MS), }); if (!response.ok) continue; await response.json(); @@ -57,26 +65,21 @@ async function findConnection(origin) { return null; } -function loadPanel(baseURL) { +function loadPanel(baseURL, origin) { status.classList.add('hidden'); frame.style.display = 'block'; // The SPA is bundled inside the extension at ui/index.html const panelUrl = chrome.runtime.getURL('ui/index.html'); - if (baseURL) { - // Get the inspected page's origin to build the full baseURL - chrome.devtools.inspectedWindow.eval('location.origin', (origin) => { - const url = new URL(baseURL, origin); - if (!LOCAL_HOSTS.includes(url.hostname)) { - frame.src = panelUrl; - return; - } - frame.src = `${panelUrl}?baseURL=${encodeURIComponent(url.href)}`; - }); - } else { + if (!baseURL || !origin) { frame.src = panelUrl; + return; } + const url = new URL(baseURL, origin); + frame.src = LOCAL_HOSTS.includes(url.hostname) + ? `${panelUrl}?baseURL=${encodeURIComponent(url.href)}` + : panelUrl; } // Start detection after a short delay to let the page settle From aed5ebd4861cc0d8e30d67d8cf71bab83187d1db Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 26 Sep 2026 13:13:08 +0300 Subject: [PATCH 3/3] fix(extension): do not follow redirects when probing for devframe --- extension/panel-bridge.js | 1 + 1 file changed, 1 insertion(+) diff --git a/extension/panel-bridge.js b/extension/panel-bridge.js index 23e3a08..9333685 100644 --- a/extension/panel-bridge.js +++ b/extension/panel-bridge.js @@ -52,6 +52,7 @@ async function findConnection(origin) { const response = await fetch(new URL(base + file, origin), { credentials: 'omit', cache: 'no-store', + redirect: 'error', signal: AbortSignal.timeout(PROBE_TIMEOUT_MS), }); if (!response.ok) continue;