From 79ca796b51a6fafe0ec341e6b59d2ba06a10082b Mon Sep 17 00:00:00 2001 From: iamEvan <47493765+iamEvanYT@users.noreply.github.com> Date: Sat, 10 May 2025 13:30:57 +0100 Subject: [PATCH 1/2] feat: native adblocker --- bun.lock | 27 ++++++ package.json | 1 + src/main/index.ts | 1 + src/main/modules/basic-settings.ts | 29 +++++- src/main/modules/content-blocker.ts | 137 ++++++++++++++++++++++++++++ src/main/modules/output.ts | 3 +- 6 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 src/main/modules/content-blocker.ts diff --git a/bun.lock b/bun.lock index 4ddf9aca..bb6a9a57 100644 --- a/bun.lock +++ b/bun.lock @@ -6,6 +6,7 @@ "dependencies": { "@electron-toolkit/preload": "^3.0.1", "@electron-toolkit/utils": "^4.0.0", + "@ghostery/adblocker-electron": "^2.5.2", "@phosphor-icons/core": "^2.1.1", "better-sqlite3": "^11.9.1", "electron-chrome-extensions": "npm:@iamevan/electron-chrome-extensions@4.7.5", @@ -229,6 +230,16 @@ "@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="], + "@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=="], + + "@ghostery/adblocker-content": ["@ghostery/adblocker-content@2.5.2", "", { "dependencies": { "@ghostery/adblocker-extended-selectors": "^2.5.2" } }, "sha512-H3e4QZsom7HqVgIBLaoHriqRh27MyXgwC43ClidOXXbCtKn6h7c3wc9TnQssQpXpcyV7HRPmWjMtADzUc+yYKg=="], + + "@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=="], + + "@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=="], + + "@ghostery/adblocker-extended-selectors": ["@ghostery/adblocker-extended-selectors@2.5.2", "", {}, "sha512-Z2MQ4BiPTPG3cI1CFF1cE0IywL1EM2KGnOVkKEx62fnkO0aRyvAeja0jhQvjENtY/hixWLrsSg3MU95Clvq23g=="], + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], "@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 @@ "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], + "@remusao/guess-url-type": ["@remusao/guess-url-type@2.0.0", "", {}, "sha512-L98gV/X/GESt5Tgqq/PxpZYClVqeq6/5InrRKl4elq4qXbdZjHlNTgRhXb1xIaUBkikzv410sXw3QaBUYyXt8g=="], + + "@remusao/small": ["@remusao/small@2.0.0", "", {}, "sha512-1ksGCbl1hSeO4CV/uk0qv2vUdZ1ZRdIMzSn0HFsHTJnmWSDk2li+T5eCI9BtweYe0uVQhCvbMjk/3qwsN6fuYg=="], + + "@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=="], + + "@remusao/smaz-compress": ["@remusao/smaz-compress@2.1.0", "", { "dependencies": { "@remusao/trie": "^2.0.0" } }, "sha512-IyuzXxd5F1p5WAvA6Td9ny+wh3sj9szMmdZtQ8Fb5/yE4lIwujXCz0e702u62r3XU47qsQcR/CjSRaw65u7iGA=="], + + "@remusao/smaz-decompress": ["@remusao/smaz-decompress@2.1.0", "", {}, "sha512-TlM/ibBOMiCRniuBjv4x4nIcVbOb1o6DdK+p5OM+zHNndEu9za2C1Bd+PSqnsVCn15Fv2HcLVWGpqh2hKs9uuw=="], + + "@remusao/trie": ["@remusao/trie@2.0.0", "", {}, "sha512-YmVfZrd+igKXJMuAvsNdDnUAyoNw7M+9e2ij6UsaZFZGQzLd75pbxhiuFmdphEXi/s3uXe25+wtFRWjLA2Ho0Q=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.40.0", "", { "os": "android", "cpu": "arm" }, "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg=="], "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.40.0", "", { "os": "android", "cpu": "arm64" }, "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w=="], @@ -1585,6 +1608,10 @@ "tinyglobby": ["tinyglobby@0.2.13", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw=="], + "tldts-core": ["tldts-core@7.0.7", "", {}, "sha512-ECqb8imSroX1UmUuhRBNPkkmtZ8mHEenieim80UVxG0M5wXVjY2Fp2tYXCPvk+nLy1geOhFpeD5YQhM/gF63Jg=="], + + "tldts-experimental": ["tldts-experimental@7.0.7", "", { "dependencies": { "tldts-core": "^7.0.7" } }, "sha512-V055ViO8G6PbTBfaiL1Utq/MLUqFZKhJ1c9+T8t+c1uPmVSAl7rR6ib8y0lleN18niKV6f1/zmMlAhpFEaPY9w=="], + "tmp": ["tmp@0.2.3", "", {}, "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w=="], "tmp-promise": ["tmp-promise@3.0.3", "", { "dependencies": { "tmp": "^0.2.0" } }, "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ=="], diff --git a/package.json b/package.json index 520d53cb..601f5c08 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "dependencies": { "@electron-toolkit/preload": "^3.0.1", "@electron-toolkit/utils": "^4.0.0", + "@ghostery/adblocker-electron": "^2.5.2", "@phosphor-icons/core": "^2.1.1", "better-sqlite3": "^11.9.1", "electron-chrome-extensions": "npm:@iamevan/electron-chrome-extensions@4.7.5", diff --git a/src/main/index.ts b/src/main/index.ts index 4a1f4723..1c102f20 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -8,6 +8,7 @@ import { createInitialWindow } from "@/saving/tabs"; import { TabbedBrowserWindow } from "@/browser/window"; import "@/modules/auto-update"; import "@/modules/posthog"; +import "@/modules/content-blocker"; import { debugPrint } from "@/modules/output"; export let browser: Browser | null = null; diff --git a/src/main/modules/basic-settings.ts b/src/main/modules/basic-settings.ts index cbfc2686..1cb16589 100644 --- a/src/main/modules/basic-settings.ts +++ b/src/main/modules/basic-settings.ts @@ -25,6 +25,33 @@ export const BasicSettings: BasicSetting[] = [ defaultValue: true }, + // [GENERAL] Content Blocking + { + id: "contentBlocker", + name: "Content Blocker (Built-In Adblocker)", + showName: true, + type: "enum", + defaultValue: "disabled", + options: [ + { + id: "disabled", + name: "Disabled" + }, + { + id: "adsOnly", + name: "Block Ads" + }, + { + id: "adsAndTrackers", + name: "Block Ads & Trackers" + }, + { + id: "all", + name: "Block All (Cookie Notices, etc...)" + } + ] + }, + // New Tab Mode { id: "newTabMode", @@ -128,7 +155,7 @@ export const BasicSettingCards: BasicSettingCard[] = [ { title: "General Settings", subtitle: "General settings for the application", - settings: ["autoUpdate", "internal_setAsDefaultBrowser"] + settings: ["autoUpdate", "contentBlocker", "internal_setAsDefaultBrowser"] }, // Update Card (Internal) diff --git a/src/main/modules/content-blocker.ts b/src/main/modules/content-blocker.ts new file mode 100644 index 00000000..b395e1ba --- /dev/null +++ b/src/main/modules/content-blocker.ts @@ -0,0 +1,137 @@ +import { browser } from "@/index"; +import { debugPrint } from "@/modules/output"; +import { getSettingValueById, onSettingsCached, settingsEmitter } from "@/saving/settings"; +import { ElectronBlocker } from "@ghostery/adblocker-electron"; +import { Session } from "electron"; + +type BlockerInstanceType = "all" | "adsAndTrackers" | "adsOnly"; + +/** + * ContentBlocker class manages ad and tracking content blocking functionality + */ +class ContentBlocker { + private blockerInstancePromise: Promise | undefined = undefined; + private blockerInstanceType: BlockerInstanceType | undefined = undefined; + private blockedSessions: Session[] = []; + + /** + * Creates or returns existing blocker instance of the specified type + */ + private async createBlockerInstance(type: BlockerInstanceType): Promise { + if (this.blockerInstancePromise && this.blockerInstanceType === type) { + return this.blockerInstancePromise; + } + + if (this.blockerInstancePromise) { + await this.disableBlocker(); + } + + debugPrint("CONTENT_BLOCKER", "Creating blocker instance:", type); + switch (type) { + case "all": + this.blockerInstancePromise = ElectronBlocker.fromPrebuiltFull(); + break; + case "adsAndTrackers": + this.blockerInstancePromise = ElectronBlocker.fromPrebuiltAdsAndTracking(); + break; + case "adsOnly": + this.blockerInstancePromise = ElectronBlocker.fromPrebuiltAdsOnly(); + break; + } + + this.blockerInstancePromise.then((blocker) => { + blocker.on("request-blocked", (request) => { + debugPrint("CONTENT_BLOCKER", "Request blocked:", request.url); + }); + }); + + this.blockerInstanceType = type; + return this.blockerInstancePromise as Promise; + } + + /** + * Disables content blocking on all sessions + */ + private async disableBlocker(): Promise { + if (!this.blockerInstancePromise) return; + + const blocker = await this.blockerInstancePromise; + for (const session of this.blockedSessions) { + blocker.disableBlockingInSession(session); + } + + this.blockedSessions = []; + this.blockerInstancePromise = undefined; + this.blockerInstanceType = undefined; + } + + /** + * Enables content blocking for a specific session + */ + private async enableBlockerForSession(blockerType: BlockerInstanceType, session: Session): Promise { + const blocker = await this.createBlockerInstance(blockerType); + if (!blocker) return; + + // check if session is already blocked + if (this.blockedSessions.includes(session)) return; + + // add session to blocked sessions + this.blockedSessions.push(session); + + // enable blocking in session + blocker.enableBlockingInSession(session); + } + + /** + * Updates content blocker configuration based on user settings + */ + public async updateConfig(): Promise { + if (!browser) return; + + const contentBlocker = getSettingValueById("contentBlocker") as string | undefined; + if (!contentBlocker) return; + + const profiles = browser.getLoadedProfiles(); + + switch (contentBlocker) { + case "all": + case "adsAndTrackers": + case "adsOnly": + for (const profile of profiles) { + this.enableBlockerForSession(contentBlocker as BlockerInstanceType, profile.session); + } + break; + default: + this.disableBlocker(); + } + + debugPrint("CONTENT_BLOCKER", "Content blocker configuration updated:", contentBlocker); + } + + /** + * Initializes content blocker and sets up event listeners + */ + public async initialize(): Promise { + // Initial configuration + await this.updateConfig(); + + // Listen for setting changes + settingsEmitter.on("settings-changed", () => { + this.updateConfig(); + }); + + // Listen for profile changes + browser?.on("profile-loaded", () => { + this.updateConfig(); + }); + } +} + +// Export singleton instance +export const contentBlocker = new ContentBlocker(); + +// Initialize content blocker when module is loaded +onSettingsCached().then(() => { + debugPrint("CONTENT_BLOCKER", "Initializing content blocker"); + contentBlocker.initialize(); +}); diff --git a/src/main/modules/output.ts b/src/main/modules/output.ts index 169b0bc2..030550a1 100644 --- a/src/main/modules/output.ts +++ b/src/main/modules/output.ts @@ -14,7 +14,8 @@ const DEBUG_AREAS = { SPACES: false, // @/sessions/spaces.ts ICONS: false, // @/modules/icons.ts PORTAL_COMPONENTS: false, // @/browser/components/portal-component-windows.ts - AUTO_UPDATER: false // @/modules/auto-update.ts + AUTO_UPDATER: false, // @/modules/auto-update.ts + CONTENT_BLOCKER: false // @/modules/content-blocker.ts } as const; export type DEBUG_AREA = keyof typeof DEBUG_AREAS; From b9700a2d3bbc80b84940eaeed2cbaacfb153aaf4 Mon Sep 17 00:00:00 2001 From: iamEvan <47493765+iamEvanYT@users.noreply.github.com> Date: Sat, 10 May 2025 13:37:08 +0100 Subject: [PATCH 2/2] fix --- src/main/modules/content-blocker.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/modules/content-blocker.ts b/src/main/modules/content-blocker.ts index b395e1ba..e9297505 100644 --- a/src/main/modules/content-blocker.ts +++ b/src/main/modules/content-blocker.ts @@ -89,8 +89,6 @@ class ContentBlocker { if (!browser) return; const contentBlocker = getSettingValueById("contentBlocker") as string | undefined; - if (!contentBlocker) return; - const profiles = browser.getLoadedProfiles(); switch (contentBlocker) {