Skip to content

Commit 9887ca6

Browse files
committed
feat(site-blocklist): add site-specific blocklist with UI and sync storage support
Signed-off-by: samzong <[email protected]>
1 parent 139feb8 commit 9887ca6

File tree

21 files changed

+1635
-7
lines changed

21 files changed

+1635
-7
lines changed

docs/website-blocklist.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Website Blocklist Design (Issue #67)
2+
3+
## Context
4+
Users need a way to disable TabBoost content-script features on specific sites (e.g., DevTools, browser terminals) where global listeners conflict with critical workflows. Goal: configurable site blocklist without disabling the entire extension.
5+
6+
## Goals
7+
- **Site-scoped opt-out**: Disable all content-script features (link preview, split-view, save intercept, notifications) on selected domains.
8+
- **Early evaluation**: Check blocklist before DOM listeners attach.
9+
- **Management UI**: Advanced Settings tab in options page for add/remove entries.
10+
- **Quick disable**: Popup button to add current site to blocklist.
11+
- **Sync storage**: Persist via `chrome.storage.sync` with quota limits and deduplication.
12+
13+
## Non-goals
14+
- Per-feature toggles (MVP: all-or-nothing per site).
15+
- `chrome.commands` shortcuts remain global (not affected).
16+
- Dynamic permission or `declarativeNetRequest` changes.
17+
18+
## UX Summary
19+
20+
### Options Page - Advanced Settings Tab
21+
- New "Advanced Settings" tab with "Website Blocklist" card.
22+
- Empty list by default.
23+
- Add input accepts:
24+
- Domain names: `example.com`, `www.example.com`, `sub.example.com`
25+
- Wildcard patterns: `*.example.com` (matches `a.example.com`, `b.example.com`, etc.)
26+
- Full URLs: protocol stripped during normalization
27+
- Inline delete button per entry.
28+
- Autosave on add/remove.
29+
30+
### Extension Popup
31+
- "Disable on this site" button.
32+
- On click: extracts current tab domain, normalizes, adds to blocklist, shows notification.
33+
- Button state reflects blocked status.
34+
- Hidden on `chrome://` and extension pages.
35+
36+
## Data Model
37+
```ts
38+
interface SiteBlocklistEntry {
39+
id: string;
40+
pattern: string; // normalized lowercase
41+
matchType: "domain" | "wildcard" | "prefix" | "regex";
42+
}
43+
44+
interface SiteBlocklistConfig {
45+
version: 1;
46+
entries: SiteBlocklistEntry[];
47+
}
48+
```
49+
50+
Storage:
51+
- `siteBlocklistConfig` (sync): main config
52+
- `siteBlocklistCompiled` (local): compiled matcher cache
53+
54+
Default: `{ version: 1, entries: [] }`
55+
56+
## Architecture
57+
58+
### Site Matching (`src/utils/siteBlocklist.js`)
59+
New module:
60+
- **Normalize**: strip protocol, lowercase, hostname-only.
61+
- **Match types**:
62+
- `*.example.com` → wildcard (matches `a.example.com`, `b.example.com`)
63+
- `example.com` → domain (matches `example.com` and subdomains)
64+
- Full URL with `/` → prefix
65+
- `/regex/` → regex (with safety limits)
66+
- **Compile**: build matcher objects, expose `shouldBypass(url)`.
67+
- **Cache**: store compiled regexes in local storage.
68+
69+
### Content Script (`src/js/contentScript.js`)
70+
1. Fetch blocklist config before attaching listeners.
71+
2. If `shouldBypass(window.location.href)`:
72+
- Set `window.__tabboostDisabled = true`
73+
- Abort initialization (minimal message handler only)
74+
3. Listen to `chrome.storage.onChanged` for updates; teardown if newly blocked.
75+
76+
### Background (`src/js/background.js`)
77+
Message handlers:
78+
- `getSiteBlocklistConfig`: return current config
79+
- `setSiteBlocklistConfig`: validate, write to sync storage
80+
- `addSiteToBlocklist`: extract domain from tab, normalize, add entry
81+
- `isTabBlocked(tabUrl)`: check if URL matches blocklist
82+
83+
### Options UI (`src/options/options.html/js`)
84+
- HTML: Advanced Settings tab with blocklist card (list, add input, delete buttons).
85+
- JS: Load config, validate/normalize patterns, autosave on changes, show errors.
86+
87+
### Popup UI (`src/popup/popup.html/js`)
88+
- Check blocked status on open.
89+
- "Disable on this site" button: extract domain, send `addSiteToBlocklist` message.
90+
- Update button state after changes.
91+
92+
### Localization
93+
New keys: `blocklistSectionTitle`, `blocklistAddPlaceholder`, `blocklistHelper`, `blocklistDuplicateError`, `blocklistInvalidPattern`, `popupDisableOnSite`, `popupEnableOnSite`, `popupSiteAddedToBlocklist`.
94+
95+
## Edge Cases
96+
- Protocol-restricted pages (`chrome://`): short-circuit gracefully, hide popup button.
97+
- Quota limits: max 200 entries, deduplicate before save.
98+
- Regex safety: max 256 chars, wrap in try/catch.
99+
- Race conditions: use `await fetchSharedSyncValues` before listeners (top-level await or async IIFE).
100+
101+
## Testing
102+
- Unit tests: pattern normalization, matching logic, wildcard/domain behavior.
103+
- Integration tests: content script initialization gating, popup flow.
104+
- Manual QA: add/remove entries, sync across profiles, teardown on mid-session changes.
105+
106+
## Rollout
107+
1. Default empty list (no behavior change).
108+
2. Migration: backfill `{ version: 1, entries: [] }` for existing users.
109+
3. Update docs after verification.

src/_locales/de/messages.json

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,5 +478,65 @@
478478
"retryLoading": {
479479
"message": "Erneut laden",
480480
"description": "Button text to retry iframe loading."
481+
},
482+
"advancedSettings": {
483+
"message": "Erweiterte Einstellungen",
484+
"description": "Advanced settings tab title"
485+
},
486+
"blocklistSectionTitle": {
487+
"message": "Website-Blockierungsliste",
488+
"description": "Title for website blocklist section"
489+
},
490+
"blocklistSectionDescription": {
491+
"message": "TabBoost-Funktionen auf bestimmten Websites deaktivieren",
492+
"description": "Description for website blocklist section"
493+
},
494+
"blocklistAddPlaceholder": {
495+
"message": "Domain eingeben (z.B. example.com, *.example.com)",
496+
"description": "Placeholder for blocklist add input"
497+
},
498+
"blocklistAddButton": {
499+
"message": "Hinzufügen",
500+
"description": "Button to add domain to blocklist"
501+
},
502+
"blocklistDeleteButton": {
503+
"message": "Löschen",
504+
"description": "Button to delete domain from blocklist"
505+
},
506+
"blocklistHelper": {
507+
"message": "Unterstützt exakte Domains, Platzhalter (*.example.com) und Unterdomains",
508+
"description": "Helper text for blocklist input"
509+
},
510+
"blocklistDuplicateError": {
511+
"message": "Diese Domain ist bereits in der Blockierungsliste",
512+
"description": "Error message when domain already exists in blocklist"
513+
},
514+
"blocklistInvalidPattern": {
515+
"message": "Ungültiges Domain-Muster",
516+
"description": "Error message for invalid domain pattern"
517+
},
518+
"blocklistSaved": {
519+
"message": "Blockierungsliste aktualisiert",
520+
"description": "Success message when blocklist is saved"
521+
},
522+
"blocklistEmpty": {
523+
"message": "Keine blockierten Websites. Fügen Sie oben Domains hinzu, um TabBoost auf bestimmten Websites zu deaktivieren.",
524+
"description": "Message shown when blocklist is empty"
525+
},
526+
"popupDisableOnSite": {
527+
"message": "Auf dieser Website deaktivieren",
528+
"description": "Button to disable TabBoost on current site"
529+
},
530+
"popupEnableOnSite": {
531+
"message": "Auf dieser Website aktivieren",
532+
"description": "Button to enable TabBoost on current site"
533+
},
534+
"popupSiteAddedToBlocklist": {
535+
"message": "Website zur Blockierungsliste hinzugefügt. Seite neu laden, um anzuwenden.",
536+
"description": "Notification when site is added to blocklist from popup"
537+
},
538+
"popupSiteRemovedFromBlocklist": {
539+
"message": "Website aus Blockierungsliste entfernt. Seite neu laden, um anzuwenden.",
540+
"description": "Notification when site is removed from blocklist from popup"
481541
}
482542
}

src/_locales/en/messages.json

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@
136136
"description": "Preview description"
137137
},
138138
"iframeCompatibility": {
139-
"message": "Iframe Compatibility Settings",
139+
"message": "Iframe Compatibility",
140140
"description": "Title for iframe compatibility settings tab"
141141
},
142142
"closeSplitView": {
@@ -164,7 +164,7 @@
164164
"description": "Notification when split view fails to load"
165165
},
166166
"generalSettings": {
167-
"message": "General Settings",
167+
"message": "General",
168168
"description": "General settings section title"
169169
},
170170
"browsingMode": {
@@ -196,7 +196,7 @@
196196
"description": "Description for the general settings section"
197197
},
198198
"viewModeSettings": {
199-
"message": "View Mode Settings",
199+
"message": "View Mode",
200200
"description": "Title for view mode settings tab"
201201
},
202202
"splitViewDescription": {
@@ -580,5 +580,65 @@
580580
"retryLoading": {
581581
"message": "Retry loading",
582582
"description": "Button text to retry iframe loading."
583+
},
584+
"advancedSettings": {
585+
"message": "Advanced",
586+
"description": "Advanced settings tab title"
587+
},
588+
"blocklistSectionTitle": {
589+
"message": "Website Blocklist",
590+
"description": "Title for website blocklist section"
591+
},
592+
"blocklistSectionDescription": {
593+
"message": "Disable TabBoost features on specific websites",
594+
"description": "Description for website blocklist section"
595+
},
596+
"blocklistAddPlaceholder": {
597+
"message": "Enter domain (e.g., example.com, *.example.com)",
598+
"description": "Placeholder for blocklist add input"
599+
},
600+
"blocklistAddButton": {
601+
"message": "Add",
602+
"description": "Button to add domain to blocklist"
603+
},
604+
"blocklistDeleteButton": {
605+
"message": "Delete",
606+
"description": "Button to delete domain from blocklist"
607+
},
608+
"blocklistHelper": {
609+
"message": "Supports exact domains, wildcards (*.example.com), and subdomains",
610+
"description": "Helper text for blocklist input"
611+
},
612+
"blocklistDuplicateError": {
613+
"message": "This domain is already in the blocklist",
614+
"description": "Error message when domain already exists in blocklist"
615+
},
616+
"blocklistInvalidPattern": {
617+
"message": "Invalid domain pattern",
618+
"description": "Error message for invalid domain pattern"
619+
},
620+
"blocklistSaved": {
621+
"message": "Blocklist updated",
622+
"description": "Success message when blocklist is saved"
623+
},
624+
"blocklistEmpty": {
625+
"message": "No blocked sites. Add domains above to disable TabBoost on specific websites.",
626+
"description": "Message shown when blocklist is empty"
627+
},
628+
"popupDisableOnSite": {
629+
"message": "Disable on this site",
630+
"description": "Button to disable TabBoost on current site"
631+
},
632+
"popupEnableOnSite": {
633+
"message": "Enable on this site",
634+
"description": "Button to enable TabBoost on current site"
635+
},
636+
"popupSiteAddedToBlocklist": {
637+
"message": "Site added to blocklist. Reload page to apply.",
638+
"description": "Notification when site is added to blocklist from popup"
639+
},
640+
"popupSiteRemovedFromBlocklist": {
641+
"message": "Site removed from blocklist. Reload page to apply.",
642+
"description": "Notification when site is removed from blocklist from popup"
583643
}
584644
}

src/_locales/es/messages.json

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,5 +478,65 @@
478478
"retryLoading": {
479479
"message": "Reintentar carga",
480480
"description": "Button text to retry iframe loading."
481+
},
482+
"advancedSettings": {
483+
"message": "Avanzado Configuración",
484+
"description": "Advanced settings tab title"
485+
},
486+
"blocklistSectionTitle": {
487+
"message": "Lista de bloqueo de sitios web",
488+
"description": "Title for website blocklist section"
489+
},
490+
"blocklistSectionDescription": {
491+
"message": "Desactivar funciones de TabBoost en sitios web específicos",
492+
"description": "Description for website blocklist section"
493+
},
494+
"blocklistAddPlaceholder": {
495+
"message": "Ingresar dominio (ej: example.com, *.example.com)",
496+
"description": "Placeholder for blocklist add input"
497+
},
498+
"blocklistAddButton": {
499+
"message": "Agregar",
500+
"description": "Button to add domain to blocklist"
501+
},
502+
"blocklistDeleteButton": {
503+
"message": "Eliminar",
504+
"description": "Button to delete domain from blocklist"
505+
},
506+
"blocklistHelper": {
507+
"message": "Admite dominios exactos, comodines (*.example.com) y subdominios",
508+
"description": "Helper text for blocklist input"
509+
},
510+
"blocklistDuplicateError": {
511+
"message": "Este dominio ya está en la lista de bloqueo",
512+
"description": "Error message when domain already exists in blocklist"
513+
},
514+
"blocklistInvalidPattern": {
515+
"message": "Patrón de dominio inválido",
516+
"description": "Error message for invalid domain pattern"
517+
},
518+
"blocklistSaved": {
519+
"message": "Lista de bloqueo actualizada",
520+
"description": "Success message when blocklist is saved"
521+
},
522+
"blocklistEmpty": {
523+
"message": "No hay sitios bloqueados. Agregue dominios arriba para desactivar TabBoost en sitios web específicos.",
524+
"description": "Message shown when blocklist is empty"
525+
},
526+
"popupDisableOnSite": {
527+
"message": "Desactivar en este sitio",
528+
"description": "Button to disable TabBoost on current site"
529+
},
530+
"popupEnableOnSite": {
531+
"message": "Activar en este sitio",
532+
"description": "Button to enable TabBoost on current site"
533+
},
534+
"popupSiteAddedToBlocklist": {
535+
"message": "Sitio agregado a la lista de bloqueo. Recargue la página para aplicar.",
536+
"description": "Notification when site is added to blocklist from popup"
537+
},
538+
"popupSiteRemovedFromBlocklist": {
539+
"message": "Sitio eliminado de la lista de bloqueo. Recargue la página para aplicar.",
540+
"description": "Notification when site is removed from blocklist from popup"
481541
}
482542
}

0 commit comments

Comments
 (0)