Skip to content

Commit 7c81b00

Browse files
committed
Merge branch 'release/v5.4' into fix/AG-26563
2 parents a170ebf + b4a703e commit 7c81b00

File tree

88 files changed

+1851
-1262
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+1851
-1262
lines changed

DEVELOPMENT.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- [Hotfix filters for MV3 with skip review](#dev-hotfix-mv3)
1313
- [Linter](#dev-linter)
1414
- [TypeScript Configuration](#dev-typescript-configs)
15+
- [CSS Architecture](#dev-css)
1516
- [Update localizations](#dev-localizations)
1617
- [Bundle Size Monitoring](#dev-bundle-size-monitoring)
1718

@@ -642,6 +643,16 @@ The project contains **5 TypeScript configuration files**, each serving a specif
642643
- Special path mapping for JSX files
643644
- Separate from compilation projects
644645

646+
### <a name="dev-css"></a> CSS Cascade Layers
647+
648+
The project uses two CSS layers to manage style priority:
649+
650+
- **`components`** - Component-specific styles (buttons, modals, etc.)
651+
- **`utilities`** - Utility classes with higher priority (e.g., `.hideOnMobile`)
652+
653+
Layer order is declared inline in HTML templates (e.g., `Extension/pages/options/index.html`):
654+
This ensures the layer order is established before any `style-loader` injections from JavaScript bundles. The `utilities` layer has higher priority than `components`, ensuring utility classes always override component styles.
655+
645656
### <a name="dev-localizations"></a> Update localizations
646657

647658
For detailed localization workflow and best practices, see [Locales Documentation](./tools/locales/README.md#dev-locales).

Extension/_locales/en/messages.json

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,10 @@
247247
"message": "Filters"
248248
},
249249
"options_antibanner_rules_count": {
250-
"message": "Filter rules count: %rules_count%"
250+
"message": "Rules: %rules_count%"
251+
},
252+
"options_antibanner_updated": {
253+
"message": "Updated: %date%"
251254
},
252255
"options_antibanner_custom_group": {
253256
"message": "Custom"
@@ -291,6 +294,9 @@
291294
"options_editor_indicator_saved": {
292295
"message": "Saved"
293296
},
297+
"options_editor_save_error": {
298+
"message": "Failed to save the changes. Try again or contact support"
299+
},
294300
"options_popup_import_error_required_privacy_permission": {
295301
"message": "To import this settings file, allow AdGuard to change your privacy-related settings."
296302
},
@@ -439,7 +445,7 @@
439445
"message": "Error updating filters"
440446
},
441447
"options_popup_update_not_found": {
442-
"message": "No updates found"
448+
"message": "Your filters are up to date"
443449
},
444450
"options_popup_update_error": {
445451
"message": "Failed to get updates. Please try again later."
@@ -992,6 +998,9 @@
992998
"options_show_adguard_full_version_title": {
993999
"message": "Show information on the AdGuard full version"
9941000
},
1001+
"options_clear_stats_confirm_modal_subtitle": {
1002+
"message": "All stats on data usage will be permanently deleted"
1003+
},
9951004
"options_show_app_updated_notification": {
9961005
"message": "Notify about extension updates"
9971006
},
@@ -1274,6 +1283,9 @@
12741283
"options_reset_settings_confirm_modal_title": {
12751284
"message": "Reset settings?"
12761285
},
1286+
"options_reset_settings_confirm_modal_subtitle": {
1287+
"message": "All settings will be reset to their default values"
1288+
},
12771289
"options_reset_settings_confirm_modal_clear_button": {
12781290
"message": "Reset"
12791291
},

Extension/assets/css/layers.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@layer components, utilities;

Extension/pages/options/index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
<meta name="viewport"
77
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
88
<meta http-equiv="X-UA-Compatible" content="ie=edge">
9+
<!-- CSS layer order must be loaded before any style-loader injections -->
10+
<link rel="stylesheet" href="../../assets/css/layers.css" />
911
<link rel="icon" type="image/svg+xml" href="../../assets/images/logo-shield.svg" />
1012
</head>
1113

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/**
2+
* Copyright (c) 2015-2026 Adguard Software Ltd.
3+
*
4+
* @file
5+
* This file is part of AdGuard Browser Extension (https://github.com/AdguardTeam/AdguardBrowserExtension).
6+
*
7+
* AdGuard Browser Extension is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* AdGuard Browser Extension is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15+
* See the GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with AdGuard Browser Extension. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
21+
import { NotifierType } from '../../../common/constants';
22+
import {
23+
MessageType,
24+
type LoadCustomFilterInfoMessage,
25+
type SubscribeToCustomFilterMessage,
26+
type RemoveAntiBannerFilterMessage,
27+
} from '../../../common/messages';
28+
import { CustomFilterApi, type GetCustomFilterInfoResult } from '../../api/filters/custom';
29+
import { messageHandler } from '../../message-handler';
30+
import { notifier } from '../../notifier';
31+
import { type CategoriesFilterData } from '../../api/filters/categories';
32+
import { NotImplementedError } from '../../errors/not-implemented-error';
33+
34+
/**
35+
* Service for processing events with custom filters.
36+
*/
37+
export abstract class CustomFiltersServiceCommon {
38+
/**
39+
* Init handlers.
40+
*/
41+
static init(): void {
42+
messageHandler.addListener(MessageType.LoadCustomFilterInfo, this.onCustomFilterInfoLoad.bind(this));
43+
messageHandler.addListener(MessageType.SubscribeToCustomFilter, this.onCustomFilterSubscription.bind(this));
44+
messageHandler.addListener(MessageType.RemoveAntiBannerFilter, this.onCustomFilterRemove.bind(this));
45+
}
46+
47+
/**
48+
* Returns custom filter info for modal window.
49+
*
50+
* @param message Message data.
51+
*
52+
* @returns Custom filter info.
53+
*/
54+
static async onCustomFilterInfoLoad(message: LoadCustomFilterInfoMessage): Promise<GetCustomFilterInfoResult> {
55+
const { url, title } = message.data;
56+
57+
return CustomFilterApi.getFilterInfo(url, title);
58+
}
59+
60+
/**
61+
* Add new custom filter.
62+
*
63+
* @param message Message data.
64+
*
65+
* @returns Custom filter metadata.
66+
*/
67+
static async onCustomFilterSubscription(message: SubscribeToCustomFilterMessage): Promise<CategoriesFilterData> {
68+
const { filter } = message.data;
69+
70+
const { customUrl, name, trusted } = filter;
71+
72+
// Creates a filter and enables the group if necessary.
73+
const filterMetadata = await CustomFilterApi.createFilter({
74+
customUrl,
75+
title: name,
76+
trusted,
77+
enabled: true,
78+
});
79+
80+
await this.updateEngine();
81+
82+
notifier.notifyListeners(NotifierType.CustomFilterAdded);
83+
84+
return filterMetadata;
85+
}
86+
87+
/**
88+
* Removes a custom filter.
89+
*
90+
* If the filter was enabled, the engine will be updated.
91+
*
92+
* @param message Message data.
93+
*/
94+
static async onCustomFilterRemove(message: RemoveAntiBannerFilterMessage): Promise<void> {
95+
const { filterId } = message.data;
96+
97+
const wasEnabled = await CustomFilterApi.removeFilter(filterId);
98+
if (wasEnabled) {
99+
await this.updateEngine();
100+
}
101+
}
102+
103+
/**
104+
* Updates the filtering engine.
105+
*
106+
* @throws Error if not implemented by a subclass.
107+
*/
108+
protected static async updateEngine(): Promise<void> {
109+
throw new NotImplementedError();
110+
}
111+
}
Lines changed: 6 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright (c) 2015-2025 Adguard Software Ltd.
2+
* Copyright (c) 2015-2026 Adguard Software Ltd.
33
*
44
* @file
55
* This file is part of AdGuard Browser Extension (https://github.com/AdguardTeam/AdguardBrowserExtension).
@@ -17,87 +17,18 @@
1717
* You should have received a copy of the GNU General Public License
1818
* along with AdGuard Browser Extension. If not, see <http://www.gnu.org/licenses/>.
1919
*/
20-
21-
import { NotifierType } from '../../../common/constants';
22-
import {
23-
MessageType,
24-
type LoadCustomFilterInfoMessage,
25-
type SubscribeToCustomFilterMessage,
26-
type RemoveAntiBannerFilterMessage,
27-
} from '../../../common/messages';
28-
import { CustomFilterApi, type GetCustomFilterInfoResult } from '../../api/filters/custom';
29-
import { messageHandler } from '../../message-handler';
30-
import { notifier } from '../../notifier';
3120
import { engine } from '../../engine';
32-
import { type CategoriesFilterData } from '../../api/filters/categories';
21+
22+
import { CustomFiltersServiceCommon } from './custom-filters-service-common';
3323

3424
/**
3525
* Service for processing events with custom filters.
3626
*/
37-
export class CustomFiltersService {
38-
/**
39-
* Init handlers.
40-
*/
41-
static init(): void {
42-
messageHandler.addListener(MessageType.LoadCustomFilterInfo, CustomFiltersService.onCustomFilterInfoLoad);
43-
// eslint-disable-next-line max-len
44-
messageHandler.addListener(MessageType.SubscribeToCustomFilter, CustomFiltersService.onCustomFilterSubscription);
45-
messageHandler.addListener(MessageType.RemoveAntiBannerFilter, CustomFiltersService.onCustomFilterRemove);
46-
}
47-
48-
/**
49-
* Returns custom filter info for modal window.
50-
*
51-
* @param message Message data.
52-
*
53-
* @returns Custom filter info.
54-
*/
55-
static async onCustomFilterInfoLoad(message: LoadCustomFilterInfoMessage): Promise<GetCustomFilterInfoResult> {
56-
const { url, title } = message.data;
57-
58-
return CustomFilterApi.getFilterInfo(url, title);
59-
}
60-
27+
export class CustomFiltersService extends CustomFiltersServiceCommon {
6128
/**
62-
* Add new custom filter.
63-
*
64-
* @param message Message data.
65-
*
66-
* @returns Custom filter metadata.
29+
* @inheritdoc
6730
*/
68-
static async onCustomFilterSubscription(message: SubscribeToCustomFilterMessage): Promise<CategoriesFilterData> {
69-
const { filter } = message.data;
70-
71-
const { customUrl, name, trusted } = filter;
72-
73-
// Creates a filter and enables the group if necessary.
74-
const filterMetadata = await CustomFilterApi.createFilter({
75-
customUrl,
76-
title: name,
77-
trusted,
78-
enabled: true,
79-
});
80-
31+
protected static override async updateEngine(): Promise<void> {
8132
engine.debounceUpdate();
82-
83-
notifier.notifyListeners(NotifierType.CustomFilterAdded);
84-
85-
return filterMetadata;
86-
}
87-
88-
/**
89-
* Removes a custom filter.
90-
*
91-
* If the filter was enabled, the engine will be updated.
92-
*
93-
* @param message Message data.
94-
*/
95-
static async onCustomFilterRemove(message: RemoveAntiBannerFilterMessage): Promise<void> {
96-
const { filterId } = message.data;
97-
98-
const wasEnabled = await CustomFilterApi.removeFilter(filterId);
99-
if (wasEnabled) {
100-
engine.debounceUpdate();
101-
}
10233
}
10334
}
Lines changed: 6 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright (c) 2015-2025 Adguard Software Ltd.
2+
* Copyright (c) 2015-2026 Adguard Software Ltd.
33
*
44
* @file
55
* This file is part of AdGuard Browser Extension (https://github.com/AdguardTeam/AdguardBrowserExtension).
@@ -17,86 +17,18 @@
1717
* You should have received a copy of the GNU General Public License
1818
* along with AdGuard Browser Extension. If not, see <http://www.gnu.org/licenses/>.
1919
*/
20-
21-
import { NotifierType } from '../../../common/constants';
22-
import {
23-
MessageType,
24-
type LoadCustomFilterInfoMessage,
25-
type SubscribeToCustomFilterMessage,
26-
type RemoveAntiBannerFilterMessage,
27-
} from '../../../common/messages';
28-
import { CustomFilterApi, type GetCustomFilterInfoResult } from '../../api/filters/custom';
29-
import { messageHandler } from '../../message-handler';
30-
import { notifier } from '../../notifier';
3120
import { engine } from '../../engine';
32-
import { type CategoriesFilterData } from '../../api/filters/categories';
21+
22+
import { CustomFiltersServiceCommon } from './custom-filters-service-common';
3323

3424
/**
3525
* Service for processing events with custom filters.
3626
*/
37-
export class CustomFiltersService {
38-
/**
39-
* Init handlers.
40-
*/
41-
static init(): void {
42-
messageHandler.addListener(MessageType.LoadCustomFilterInfo, CustomFiltersService.onCustomFilterInfoLoad);
43-
// eslint-disable-next-line max-len
44-
messageHandler.addListener(MessageType.SubscribeToCustomFilter, CustomFiltersService.onCustomFilterSubscription);
45-
messageHandler.addListener(MessageType.RemoveAntiBannerFilter, CustomFiltersService.onCustomFilterRemove);
46-
}
47-
48-
/**
49-
* Returns custom filter info for modal window.
50-
*
51-
* @param message Message data.
52-
*
53-
* @returns Custom filter info.
54-
*/
55-
static async onCustomFilterInfoLoad(message: LoadCustomFilterInfoMessage): Promise<GetCustomFilterInfoResult> {
56-
const { url, title } = message.data;
57-
58-
return CustomFilterApi.getFilterInfo(url, title);
59-
}
60-
27+
export class CustomFiltersService extends CustomFiltersServiceCommon {
6128
/**
62-
* Add new custom filter.
63-
*
64-
* @param message Message data.
65-
*
66-
* @returns Custom filter metadata.
29+
* @inheritdoc
6730
*/
68-
static async onCustomFilterSubscription(message: SubscribeToCustomFilterMessage): Promise<CategoriesFilterData> {
69-
const { filter } = message.data;
70-
71-
const { customUrl, name, trusted } = filter;
72-
73-
// Creates a filter and enables the group if necessary.
74-
const filterMetadata = await CustomFilterApi.createFilter({
75-
customUrl,
76-
title: name,
77-
trusted,
78-
enabled: true,
79-
});
80-
31+
protected static override async updateEngine(): Promise<void> {
8132
await engine.update();
82-
83-
notifier.notifyListeners(NotifierType.CustomFilterAdded);
84-
return filterMetadata;
85-
}
86-
87-
/**
88-
* Removes a custom filter.
89-
*
90-
* If the filter was enabled, the engine will be updated.
91-
*
92-
* @param message Message data.
93-
*/
94-
static async onCustomFilterRemove(message: RemoveAntiBannerFilterMessage): Promise<void> {
95-
const { filterId } = message.data;
96-
97-
const wasEnabled = await CustomFilterApi.removeFilter(filterId);
98-
if (wasEnabled) {
99-
await engine.update();
100-
}
10133
}
10234
}

0 commit comments

Comments
 (0)