Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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=="],
Expand Down Expand Up @@ -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=="],
Expand Down Expand Up @@ -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=="],
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
29 changes: 28 additions & 1 deletion src/main/modules/basic-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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)
Expand Down
135 changes: 135 additions & 0 deletions src/main/modules/content-blocker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
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<ElectronBlocker> | 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<ElectronBlocker> {
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<ElectronBlocker>;
}

/**
* Disables content blocking on all sessions
*/
private async disableBlocker(): Promise<void> {
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<void> {
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<void> {
if (!browser) return;

const contentBlocker = getSettingValueById("contentBlocker") as string | undefined;
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<void> {
// 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();
});
3 changes: 2 additions & 1 deletion src/main/modules/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down