Skip to content

Remove activeTab permission; make background sole icon authority#1464

Merged
igrigorik merged 1 commit into
masterfrom
claude/audit-security-icon-cleanup-TiRv9
Mar 28, 2026
Merged

Remove activeTab permission; make background sole icon authority#1464
igrigorik merged 1 commit into
masterfrom
claude/audit-security-icon-cleanup-TiRv9

Conversation

@igrigorik

Copy link
Copy Markdown
Owner

── What this is ──

This is an architectural tidy-up, not a feature change. It removes a
redundant permission and eliminates a dual-writer pattern on the
toolbar icon. No user-visible behaviour changes.

── Permission model (activeTab removal) ──

The manifest declared both broad host-matched content_scripts and the
activeTab permission. activeTab grants temporary host access to the
active tab when the user clicks the toolbar icon. But because the
extension already injects content scripts on http, https, and file URLs
via the content_scripts manifest key, Chrome grants permanent host
permissions for those origins. chrome.tabs.query() and
chrome.tabs.sendMessage() — the only two APIs that consumed activeTab,
both in popup.js — work identically without it.

activeTab was likely carried forward from the original MV2 codebase
where the permission landscape was different. Removing it shrinks the
install-time permission surface with zero functional cost.

── Icon update consolidation ──

Before this change, toggling the extension via the popup power button
caused the toolbar icon to be set three times per click:

  1. popup.js called chrome.action.setIcon() directly (instant feedback)
  2. popup.js sent an EXTENSION_TOGGLE runtime message → background
    onMessage handler called updateIcon()
  3. popup.js wrote { enabled } to chrome.storage.sync → background
    storage.onChanged handler called updateIcon()

Path 3 is the only one that is both necessary and sufficient. It fires
for every write to the enabled key regardless of origin (popup, options
page, future UI). Paths 1 and 2 were redundant fast-paths that created
a dual-writer race: popup and background both calling setIcon with the
same intent but no coordination, which could drift under service-worker
lifecycle edge cases (wake, suspend, stale contexts).

After this change:

  • Popup writes enabled to storage and updates its own DOM. That's it.
  • Background owns icon state exclusively via storage.onChanged.
  • The EXTENSION_TOGGLE runtime message and its handler are removed.
  • No runtime.sendMessage calls remain in popup.js.

── Icon design (unchanged, documented here for context) ──

The toolbar icon has exactly two states: enabled (colour) and disabled
(greyscale). There is no per-tab state, no speed badge, no dynamic
text overlay — this was a deliberate simplification from an earlier
design that tracked per-tab icon state (see tests/e2e/icon.e2e.js
which documents this as "ultra-simplified architecture").

Icon state is derived solely from the enabled boolean in
chrome.storage.sync. Background reads it on install, on startup, and
on every service-worker wake (initializeIcon called at module scope),
and reacts to changes via storage.onChanged. This is resilient to
service-worker suspension because Chrome persists storage.sync
independently and fires onChanged on wake.

── Files changed ──

manifest.json Remove activeTab from permissions array.
src/background.js Remove EXTENSION_TOGGLE onMessage listener.
src/ui/popup/popup.js Remove chrome.action.setIcon() call and
chrome.runtime.sendMessage() call from
toggleEnabledUI(). Function now only updates
popup DOM (class toggle + tooltip text).

── What this is ──

This is an architectural tidy-up, not a feature change. It removes a
redundant permission and eliminates a dual-writer pattern on the
toolbar icon. No user-visible behaviour changes.

── Permission model (activeTab removal) ──

The manifest declared both broad host-matched content_scripts and the
activeTab permission. activeTab grants *temporary* host access to the
active tab when the user clicks the toolbar icon. But because the
extension already injects content scripts on http, https, and file URLs
via the content_scripts manifest key, Chrome grants *permanent* host
permissions for those origins. chrome.tabs.query() and
chrome.tabs.sendMessage() — the only two APIs that consumed activeTab,
both in popup.js — work identically without it.

activeTab was likely carried forward from the original MV2 codebase
where the permission landscape was different. Removing it shrinks the
install-time permission surface with zero functional cost.

── Icon update consolidation ──

Before this change, toggling the extension via the popup power button
caused the toolbar icon to be set three times per click:

  1. popup.js called chrome.action.setIcon() directly (instant feedback)
  2. popup.js sent an EXTENSION_TOGGLE runtime message → background
     onMessage handler called updateIcon()
  3. popup.js wrote { enabled } to chrome.storage.sync → background
     storage.onChanged handler called updateIcon()

Path 3 is the only one that is both necessary and sufficient. It fires
for every write to the enabled key regardless of origin (popup, options
page, future UI). Paths 1 and 2 were redundant fast-paths that created
a dual-writer race: popup and background both calling setIcon with the
same intent but no coordination, which could drift under service-worker
lifecycle edge cases (wake, suspend, stale contexts).

After this change:
  - Popup writes enabled to storage and updates its own DOM. That's it.
  - Background owns icon state exclusively via storage.onChanged.
  - The EXTENSION_TOGGLE runtime message and its handler are removed.
  - No runtime.sendMessage calls remain in popup.js.

── Icon design (unchanged, documented here for context) ──

The toolbar icon has exactly two states: enabled (colour) and disabled
(greyscale). There is no per-tab state, no speed badge, no dynamic
text overlay — this was a deliberate simplification from an earlier
design that tracked per-tab icon state (see tests/e2e/icon.e2e.js
which documents this as "ultra-simplified architecture").

Icon state is derived solely from the enabled boolean in
chrome.storage.sync. Background reads it on install, on startup, and
on every service-worker wake (initializeIcon called at module scope),
and reacts to changes via storage.onChanged. This is resilient to
service-worker suspension because Chrome persists storage.sync
independently and fires onChanged on wake.

── Files changed ──

manifest.json         Remove activeTab from permissions array.
src/background.js     Remove EXTENSION_TOGGLE onMessage listener.
src/ui/popup/popup.js Remove chrome.action.setIcon() call and
                      chrome.runtime.sendMessage() call from
                      toggleEnabledUI(). Function now only updates
                      popup DOM (class toggle + tooltip text).
@igrigorik
igrigorik merged commit c329647 into master Mar 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants