Skip to content

Commit 79ca796

Browse files
committed
feat: native adblocker
1 parent 73add9d commit 79ca796

File tree

6 files changed

+196
-2
lines changed

6 files changed

+196
-2
lines changed

bun.lock

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"dependencies": {
77
"@electron-toolkit/preload": "^3.0.1",
88
"@electron-toolkit/utils": "^4.0.0",
9+
"@ghostery/adblocker-electron": "^2.5.2",
910
"@phosphor-icons/core": "^2.1.1",
1011
"better-sqlite3": "^11.9.1",
1112
"electron-chrome-extensions": "npm:@iamevan/electron-chrome-extensions@4.7.5",
@@ -229,6 +230,16 @@
229230

230231
"@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="],
231232

233+
"@ghostery/adblocker": ["@ghostery/adblocker@2.5.2", "", { "dependencies": { "@ghostery/adblocker-content": "^2.5.2", "@ghostery/adblocker-extended-selectors": "^2.5.2", "@remusao/guess-url-type": "^2.0.0", "@remusao/small": "^2.0.0", "@remusao/smaz": "^2.1.0", "tldts-experimental": "^7.0.0" } }, "sha512-/SLxUGPd1JISNGOPsxKfbso+uylDEvEp3umF5gQ3x8YgsEZzD6zYx7H4ZQxAvG1pZIr4p6N9PiAf2N88T1Wo1Q=="],
234+
235+
"@ghostery/adblocker-content": ["@ghostery/adblocker-content@2.5.2", "", { "dependencies": { "@ghostery/adblocker-extended-selectors": "^2.5.2" } }, "sha512-H3e4QZsom7HqVgIBLaoHriqRh27MyXgwC43ClidOXXbCtKn6h7c3wc9TnQssQpXpcyV7HRPmWjMtADzUc+yYKg=="],
236+
237+
"@ghostery/adblocker-electron": ["@ghostery/adblocker-electron@2.5.2", "", { "dependencies": { "@ghostery/adblocker": "^2.5.2", "@ghostery/adblocker-electron-preload": "^2.5.2", "tldts-experimental": "^7.0.0" }, "peerDependencies": { "electron": ">11" } }, "sha512-4q8OBH+RIBwzRYW7FVZXGmXI1mogd2zyA9/7fNnch5o8GVhWMjPaEc62rvgQds7DFm/7TXYbrwbIijDfUbu8XQ=="],
238+
239+
"@ghostery/adblocker-electron-preload": ["@ghostery/adblocker-electron-preload@2.5.2", "", { "dependencies": { "@ghostery/adblocker-content": "^2.5.2" }, "peerDependencies": { "electron": ">11" } }, "sha512-RNHMznod4hGm7Nn7m+H/+4DQ4QGQbN5YnVJGdKjFrwN692nFV3VUN86f5Isb9+ZVD8Us7XQbNzW/uN1AAOn+mA=="],
240+
241+
"@ghostery/adblocker-extended-selectors": ["@ghostery/adblocker-extended-selectors@2.5.2", "", {}, "sha512-Z2MQ4BiPTPG3cI1CFF1cE0IywL1EM2KGnOVkKEx62fnkO0aRyvAeja0jhQvjENtY/hixWLrsSg3MU95Clvq23g=="],
242+
232243
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
233244

234245
"@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="],
@@ -393,6 +404,18 @@
393404

394405
"@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="],
395406

407+
"@remusao/guess-url-type": ["@remusao/guess-url-type@2.0.0", "", {}, "sha512-L98gV/X/GESt5Tgqq/PxpZYClVqeq6/5InrRKl4elq4qXbdZjHlNTgRhXb1xIaUBkikzv410sXw3QaBUYyXt8g=="],
408+
409+
"@remusao/small": ["@remusao/small@2.0.0", "", {}, "sha512-1ksGCbl1hSeO4CV/uk0qv2vUdZ1ZRdIMzSn0HFsHTJnmWSDk2li+T5eCI9BtweYe0uVQhCvbMjk/3qwsN6fuYg=="],
410+
411+
"@remusao/smaz": ["@remusao/smaz@2.1.0", "", { "dependencies": { "@remusao/smaz-compress": "^2.1.0", "@remusao/smaz-decompress": "^2.1.0" } }, "sha512-hTn/ZuBY4LYaqvprTdW3U+uU4xrw5JusxqHIhUzroQlrT6l4LFQRi+aJE/SOv09iS7eO/pqg6Ec3Go+gKiWH8A=="],
412+
413+
"@remusao/smaz-compress": ["@remusao/smaz-compress@2.1.0", "", { "dependencies": { "@remusao/trie": "^2.0.0" } }, "sha512-IyuzXxd5F1p5WAvA6Td9ny+wh3sj9szMmdZtQ8Fb5/yE4lIwujXCz0e702u62r3XU47qsQcR/CjSRaw65u7iGA=="],
414+
415+
"@remusao/smaz-decompress": ["@remusao/smaz-decompress@2.1.0", "", {}, "sha512-TlM/ibBOMiCRniuBjv4x4nIcVbOb1o6DdK+p5OM+zHNndEu9za2C1Bd+PSqnsVCn15Fv2HcLVWGpqh2hKs9uuw=="],
416+
417+
"@remusao/trie": ["@remusao/trie@2.0.0", "", {}, "sha512-YmVfZrd+igKXJMuAvsNdDnUAyoNw7M+9e2ij6UsaZFZGQzLd75pbxhiuFmdphEXi/s3uXe25+wtFRWjLA2Ho0Q=="],
418+
396419
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.40.0", "", { "os": "android", "cpu": "arm" }, "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg=="],
397420

398421
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.40.0", "", { "os": "android", "cpu": "arm64" }, "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w=="],
@@ -1585,6 +1608,10 @@
15851608

15861609
"tinyglobby": ["tinyglobby@0.2.13", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw=="],
15871610

1611+
"tldts-core": ["tldts-core@7.0.7", "", {}, "sha512-ECqb8imSroX1UmUuhRBNPkkmtZ8mHEenieim80UVxG0M5wXVjY2Fp2tYXCPvk+nLy1geOhFpeD5YQhM/gF63Jg=="],
1612+
1613+
"tldts-experimental": ["tldts-experimental@7.0.7", "", { "dependencies": { "tldts-core": "^7.0.7" } }, "sha512-V055ViO8G6PbTBfaiL1Utq/MLUqFZKhJ1c9+T8t+c1uPmVSAl7rR6ib8y0lleN18niKV6f1/zmMlAhpFEaPY9w=="],
1614+
15881615
"tmp": ["tmp@0.2.3", "", {}, "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w=="],
15891616

15901617
"tmp-promise": ["tmp-promise@3.0.3", "", { "dependencies": { "tmp": "^0.2.0" } }, "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ=="],

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"dependencies": {
3030
"@electron-toolkit/preload": "^3.0.1",
3131
"@electron-toolkit/utils": "^4.0.0",
32+
"@ghostery/adblocker-electron": "^2.5.2",
3233
"@phosphor-icons/core": "^2.1.1",
3334
"better-sqlite3": "^11.9.1",
3435
"electron-chrome-extensions": "npm:@iamevan/electron-chrome-extensions@4.7.5",

src/main/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { createInitialWindow } from "@/saving/tabs";
88
import { TabbedBrowserWindow } from "@/browser/window";
99
import "@/modules/auto-update";
1010
import "@/modules/posthog";
11+
import "@/modules/content-blocker";
1112
import { debugPrint } from "@/modules/output";
1213

1314
export let browser: Browser | null = null;

src/main/modules/basic-settings.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,33 @@ export const BasicSettings: BasicSetting[] = [
2525
defaultValue: true
2626
},
2727

28+
// [GENERAL] Content Blocking
29+
{
30+
id: "contentBlocker",
31+
name: "Content Blocker (Built-In Adblocker)",
32+
showName: true,
33+
type: "enum",
34+
defaultValue: "disabled",
35+
options: [
36+
{
37+
id: "disabled",
38+
name: "Disabled"
39+
},
40+
{
41+
id: "adsOnly",
42+
name: "Block Ads"
43+
},
44+
{
45+
id: "adsAndTrackers",
46+
name: "Block Ads & Trackers"
47+
},
48+
{
49+
id: "all",
50+
name: "Block All (Cookie Notices, etc...)"
51+
}
52+
]
53+
},
54+
2855
// New Tab Mode
2956
{
3057
id: "newTabMode",
@@ -128,7 +155,7 @@ export const BasicSettingCards: BasicSettingCard[] = [
128155
{
129156
title: "General Settings",
130157
subtitle: "General settings for the application",
131-
settings: ["autoUpdate", "internal_setAsDefaultBrowser"]
158+
settings: ["autoUpdate", "contentBlocker", "internal_setAsDefaultBrowser"]
132159
},
133160

134161
// Update Card (Internal)
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { browser } from "@/index";
2+
import { debugPrint } from "@/modules/output";
3+
import { getSettingValueById, onSettingsCached, settingsEmitter } from "@/saving/settings";
4+
import { ElectronBlocker } from "@ghostery/adblocker-electron";
5+
import { Session } from "electron";
6+
7+
type BlockerInstanceType = "all" | "adsAndTrackers" | "adsOnly";
8+
9+
/**
10+
* ContentBlocker class manages ad and tracking content blocking functionality
11+
*/
12+
class ContentBlocker {
13+
private blockerInstancePromise: Promise<ElectronBlocker> | undefined = undefined;
14+
private blockerInstanceType: BlockerInstanceType | undefined = undefined;
15+
private blockedSessions: Session[] = [];
16+
17+
/**
18+
* Creates or returns existing blocker instance of the specified type
19+
*/
20+
private async createBlockerInstance(type: BlockerInstanceType): Promise<ElectronBlocker> {
21+
if (this.blockerInstancePromise && this.blockerInstanceType === type) {
22+
return this.blockerInstancePromise;
23+
}
24+
25+
if (this.blockerInstancePromise) {
26+
await this.disableBlocker();
27+
}
28+
29+
debugPrint("CONTENT_BLOCKER", "Creating blocker instance:", type);
30+
switch (type) {
31+
case "all":
32+
this.blockerInstancePromise = ElectronBlocker.fromPrebuiltFull();
33+
break;
34+
case "adsAndTrackers":
35+
this.blockerInstancePromise = ElectronBlocker.fromPrebuiltAdsAndTracking();
36+
break;
37+
case "adsOnly":
38+
this.blockerInstancePromise = ElectronBlocker.fromPrebuiltAdsOnly();
39+
break;
40+
}
41+
42+
this.blockerInstancePromise.then((blocker) => {
43+
blocker.on("request-blocked", (request) => {
44+
debugPrint("CONTENT_BLOCKER", "Request blocked:", request.url);
45+
});
46+
});
47+
48+
this.blockerInstanceType = type;
49+
return this.blockerInstancePromise as Promise<ElectronBlocker>;
50+
}
51+
52+
/**
53+
* Disables content blocking on all sessions
54+
*/
55+
private async disableBlocker(): Promise<void> {
56+
if (!this.blockerInstancePromise) return;
57+
58+
const blocker = await this.blockerInstancePromise;
59+
for (const session of this.blockedSessions) {
60+
blocker.disableBlockingInSession(session);
61+
}
62+
63+
this.blockedSessions = [];
64+
this.blockerInstancePromise = undefined;
65+
this.blockerInstanceType = undefined;
66+
}
67+
68+
/**
69+
* Enables content blocking for a specific session
70+
*/
71+
private async enableBlockerForSession(blockerType: BlockerInstanceType, session: Session): Promise<void> {
72+
const blocker = await this.createBlockerInstance(blockerType);
73+
if (!blocker) return;
74+
75+
// check if session is already blocked
76+
if (this.blockedSessions.includes(session)) return;
77+
78+
// add session to blocked sessions
79+
this.blockedSessions.push(session);
80+
81+
// enable blocking in session
82+
blocker.enableBlockingInSession(session);
83+
}
84+
85+
/**
86+
* Updates content blocker configuration based on user settings
87+
*/
88+
public async updateConfig(): Promise<void> {
89+
if (!browser) return;
90+
91+
const contentBlocker = getSettingValueById("contentBlocker") as string | undefined;
92+
if (!contentBlocker) return;
93+
94+
const profiles = browser.getLoadedProfiles();
95+
96+
switch (contentBlocker) {
97+
case "all":
98+
case "adsAndTrackers":
99+
case "adsOnly":
100+
for (const profile of profiles) {
101+
this.enableBlockerForSession(contentBlocker as BlockerInstanceType, profile.session);
102+
}
103+
break;
104+
default:
105+
this.disableBlocker();
106+
}
107+
108+
debugPrint("CONTENT_BLOCKER", "Content blocker configuration updated:", contentBlocker);
109+
}
110+
111+
/**
112+
* Initializes content blocker and sets up event listeners
113+
*/
114+
public async initialize(): Promise<void> {
115+
// Initial configuration
116+
await this.updateConfig();
117+
118+
// Listen for setting changes
119+
settingsEmitter.on("settings-changed", () => {
120+
this.updateConfig();
121+
});
122+
123+
// Listen for profile changes
124+
browser?.on("profile-loaded", () => {
125+
this.updateConfig();
126+
});
127+
}
128+
}
129+
130+
// Export singleton instance
131+
export const contentBlocker = new ContentBlocker();
132+
133+
// Initialize content blocker when module is loaded
134+
onSettingsCached().then(() => {
135+
debugPrint("CONTENT_BLOCKER", "Initializing content blocker");
136+
contentBlocker.initialize();
137+
});

src/main/modules/output.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ const DEBUG_AREAS = {
1414
SPACES: false, // @/sessions/spaces.ts
1515
ICONS: false, // @/modules/icons.ts
1616
PORTAL_COMPONENTS: false, // @/browser/components/portal-component-windows.ts
17-
AUTO_UPDATER: false // @/modules/auto-update.ts
17+
AUTO_UPDATER: false, // @/modules/auto-update.ts
18+
CONTENT_BLOCKER: false // @/modules/content-blocker.ts
1819
} as const;
1920

2021
export type DEBUG_AREA = keyof typeof DEBUG_AREAS;

0 commit comments

Comments
 (0)