mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-14 15:16:35 +03:00
Fix: surface scan failures in extension popup for local files (#261)
* Fix: surface scan failures in extension popup for local files Scanning a local file:// page with "Allow access to file URLs" off left the popup stuck on "Scanning..." because the blocked content-script injection returned silently. ensureContentScriptInjected() now returns the real error, and sendScanToTab() sends a scan-failed message that the popup renders as a small line, with a permission hint shown only for file:// tabs. Fixes #258 Co-authored-by: Cursor <cursoragent@cursor.com> * Improve: report the actual error when a non-file scan fails The generic "This page can't be scanned." gave no reason. Non-file failures now read "Couldn't scan this page: <error>" so the user sees what Chrome reported instead of a dead end. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix: scope popup broadcasts to the active tab The popup acted on every findings-updated / scan-failed / overlays broadcast regardless of which tab it targeted, so a background or DevTools-driven rescan on another tab could reset the button or show a spurious error. Cache the active tab id and ignore broadcasts for other tabs. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -58,7 +58,7 @@ async function buildScanConfig() {
|
||||
// engages with the extension (DevTools panel/sidebar opened, popup scan, etc).
|
||||
async function ensureContentScriptInjected(tabId) {
|
||||
const state = getState(tabId);
|
||||
if (state.csInjected) return true;
|
||||
if (state.csInjected) return { ok: true };
|
||||
try {
|
||||
await chrome.scripting.executeScript({
|
||||
target: { tabId },
|
||||
@@ -66,16 +66,29 @@ async function ensureContentScriptInjected(tabId) {
|
||||
injectImmediately: true,
|
||||
});
|
||||
state.csInjected = true;
|
||||
return true;
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
// Common cause: chrome:// pages, the web store, or other restricted URLs
|
||||
return false;
|
||||
// Common cause: chrome:// pages, the web store, the Chrome Web Store, or
|
||||
// file:// pages when "Allow access to file URLs" is off. Keep the real
|
||||
// error so the UI can explain what happened.
|
||||
return { ok: false, error: err?.message || String(err) };
|
||||
}
|
||||
}
|
||||
|
||||
async function sendScanToTab(tabId) {
|
||||
const ok = await ensureContentScriptInjected(tabId);
|
||||
if (!ok) return;
|
||||
const { ok, error } = await ensureContentScriptInjected(tabId);
|
||||
if (!ok) {
|
||||
// Injection was blocked. Tell an open popup why so it can stop showing
|
||||
// "Scanning..." and surface a hint. The popup may be closed, so ignore
|
||||
// delivery failures.
|
||||
let url = '';
|
||||
try { url = (await chrome.tabs.get(tabId))?.url || ''; } catch { /* tab gone */ }
|
||||
const message = url.startsWith('file:')
|
||||
? 'Can\u2019t scan local files. Enable \u201CAllow access to file URLs\u201D for Impeccable in chrome://extensions.'
|
||||
: `Couldn\u2019t scan this page${error ? `: ${error}` : '.'}`;
|
||||
chrome.runtime.sendMessage({ action: 'scan-failed', tabId, message }).catch(() => {});
|
||||
return;
|
||||
}
|
||||
const config = await buildScanConfig();
|
||||
chrome.tabs.sendMessage(tabId, { action: 'scan', config }).catch(() => {});
|
||||
}
|
||||
|
||||
@@ -89,6 +89,17 @@ h1 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.scan-error {
|
||||
margin: -8px 0 16px;
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
color: oklch(70% 0.13 30);
|
||||
}
|
||||
|
||||
.scan-error[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
<button class="btn btn-secondary" id="btn-toggle">Hide overlays</button>
|
||||
</div>
|
||||
|
||||
<p class="scan-error" id="scan-error" hidden></p>
|
||||
|
||||
<footer>
|
||||
<a href="https://impeccable.style" id="link-site">impeccable.style</a>
|
||||
</footer>
|
||||
|
||||
@@ -8,8 +8,13 @@ const countNumber = document.getElementById('count-number');
|
||||
const countLabel = document.getElementById('count-label');
|
||||
const btnScan = document.getElementById('btn-scan');
|
||||
const btnToggle = document.getElementById('btn-toggle');
|
||||
const scanError = document.getElementById('scan-error');
|
||||
|
||||
let overlaysVisible = true;
|
||||
// The popup only ever reflects the active tab. Broadcasts from the service
|
||||
// worker carry a tabId, so we cache the active one and ignore updates meant
|
||||
// for other tabs (e.g. a DevTools-driven rescan failing on a background tab).
|
||||
let activeTabId = null;
|
||||
|
||||
async function getActiveTabId() {
|
||||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
@@ -29,11 +34,14 @@ function updateFromState(state) {
|
||||
async function loadState() {
|
||||
const tabId = await getActiveTabId();
|
||||
if (!tabId) return;
|
||||
activeTabId = tabId;
|
||||
chrome.runtime.sendMessage({ action: 'get-state', tabId }, updateFromState);
|
||||
}
|
||||
|
||||
// Listen for real-time updates from service worker
|
||||
chrome.runtime.onMessage.addListener((msg) => {
|
||||
// Ignore broadcasts for a tab other than the one this popup is showing.
|
||||
if (msg.tabId != null && activeTabId != null && msg.tabId !== activeTabId) return;
|
||||
if (msg.action === 'findings-updated') {
|
||||
const count = msg.findings?.reduce((sum, f) => sum + f.findings.length, 0) || 0;
|
||||
countNumber.textContent = String(count);
|
||||
@@ -41,6 +49,13 @@ chrome.runtime.onMessage.addListener((msg) => {
|
||||
countLabel.textContent = count === 1 ? 'anti-pattern' : 'anti-patterns';
|
||||
btnScan.textContent = 'Scan page';
|
||||
btnScan.disabled = false;
|
||||
scanError.hidden = true;
|
||||
}
|
||||
if (msg.action === 'scan-failed') {
|
||||
btnScan.textContent = 'Scan page';
|
||||
btnScan.disabled = false;
|
||||
scanError.textContent = msg.message || 'Couldn\u2019t scan this page.';
|
||||
scanError.hidden = false;
|
||||
}
|
||||
if (msg.action === 'overlays-toggled-broadcast') {
|
||||
overlaysVisible = msg.visible;
|
||||
@@ -51,6 +66,8 @@ chrome.runtime.onMessage.addListener((msg) => {
|
||||
btnScan.addEventListener('click', async () => {
|
||||
const tabId = await getActiveTabId();
|
||||
if (!tabId) return;
|
||||
activeTabId = tabId;
|
||||
scanError.hidden = true;
|
||||
btnScan.textContent = 'Scanning...';
|
||||
btnScan.disabled = true;
|
||||
chrome.runtime.sendMessage({ action: 'scan', tabId });
|
||||
|
||||
Reference in New Issue
Block a user