Skip to content

Commit be39a69

Browse files
committed
AG-35667 remake cosmetic injection into frames (in particular with no src) for MV2, refactoring
Squashed commit of the following: commit c7f623d Author: Slava Leleka <[email protected]> Date: Fri Jan 31 13:33:07 2025 -0500 fix changelog link commit 0c9b317 Author: Slava Leleka <[email protected]> Date: Fri Jan 31 13:27:46 2025 -0500 update tgz files commit 2e4bf5e Merge: 2c047c3 9efc8d7 Author: Slava Leleka <[email protected]> Date: Fri Jan 31 13:22:32 2025 -0500 merge the parent branch into the current branch, resolve conflicts commit 2c047c3 Author: Slava Leleka <[email protected]> Date: Fri Jan 31 13:20:21 2025 -0500 add todo for createNewTabContext commit 5805df1 Author: Slava Leleka <[email protected]> Date: Fri Jan 31 13:17:16 2025 -0500 fix comment for INJECTION_MAX_TRIES commit 5e164aa Author: Slava Leleka <[email protected]> Date: Fri Jan 31 13:16:19 2025 -0500 ditch generateDocumentId and generateParentDocumentId commit 409935f Author: Slava Leleka <[email protected]> Date: Fri Jan 31 12:45:06 2025 -0500 fix comment commit 15c04f3 Author: Slava Leleka <[email protected]> Date: Fri Jan 31 12:44:09 2025 -0500 fix type in addMarkerToInjectRule commit 49e15b9 Author: Slava Leleka <[email protected]> Date: Fri Jan 31 12:43:04 2025 -0500 move CONTENT_ATTR_RE out commit c205680 Author: Slava Leleka <[email protected]> Date: Fri Jan 31 12:40:19 2025 -0500 fix comment commit 18309ca Author: Slava Leleka <[email protected]> Date: Fri Jan 31 12:37:57 2025 -0500 add todo for TabInfoMV2 commit 7628e4e Author: Slava Leleka <[email protected]> Date: Fri Jan 31 11:25:13 2025 -0500 fix common handleTabCreate commit 8052d97 Author: Slava Leleka <[email protected]> Date: Fri Jan 31 10:53:50 2025 -0500 make filteringLog protected commit 3307ee9 Author: Slava Leleka <[email protected]> Date: Fri Jan 31 10:36:04 2025 -0500 fix readme commit 5f53118 Author: Slava Leleka <[email protected]> Date: Thu Jan 30 21:38:25 2025 -0500 fix browser compatibility commit ce5739b Author: Slava Leleka <[email protected]> Date: Thu Jan 30 21:05:10 2025 -0500 fix changelog commit 6f31230 Author: Slava Leleka <[email protected]> Date: Thu Jan 30 20:57:14 2025 -0500 update tgz files commit 31ae2a9 Author: Slava Leleka <[email protected]> Date: Thu Jan 30 20:45:42 2025 -0500 update tgz files commit 1c5fd84 Merge: 48a30d6 193e35c Author: Slava Leleka <[email protected]> Date: Thu Jan 30 20:31:00 2025 -0500 Merge branch 'master' into fix/AG-35667-mv2-002 commit 48a30d6 Author: Slava Leleka <[email protected]> Date: Thu Jan 30 20:27:20 2025 -0500 fix generateId comment ... and 1373 more commits
1 parent 9efc8d7 commit be39a69

Some content is hidden

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

42 files changed

+2166
-2599
lines changed

packages/agtree/agtree.tgz

-221 KB
Binary file not shown.

packages/tsurlfilter/CHANGELOG.md

-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3535

3636
[3.1.0-alpha.8]: https://github.com/AdguardTeam/tsurlfilter/releases/tag/tsurlfilter-v3.1.0-alpha.8
3737
[AdguardBrowserExtension#3014]: https://github.com/AdguardTeam/AdguardBrowserExtension/issues/3014
38-
[AdguardBrowserExtension#3015]: https://github.com/AdguardTeam/AdguardBrowserExtension/issues/3015
3938
[AdguardBrowserExtension#3012]: https://github.com/AdguardTeam/AdguardBrowserExtension/issues/3012
4039
[AdguardBrowserExtension#3076]: https://github.com/AdguardTeam/AdguardBrowserExtension/issues/3076
4140
[AdguardBrowserExtension#2924]: https://github.com/AdguardTeam/AdguardBrowserExtension/issues/2924

packages/tswebextension/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
- Updated [@adguard/tsurlfilter] to `v3.2.0-alpha.0`.
1818
- Renamed export `RULE_SET_NAME_PREFIX` to `RULESET_NAME_PREFIX`.
1919

20+
### Fixed
21+
22+
- Cosmetic rules injecting into `about:blank` iframes in MV2.
23+
2024
## [3.0.0-alpha.1] - 2025-01-30
2125

2226
### Added

packages/tswebextension/README.md

+11-9
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ TypeScript library that wraps webextension api for tsurlfilter library.
55
Table of content:
66

77
- [TSWebExtension](#tswebextension)
8-
- [Browser support](#browser-support)
8+
- [Browser compatibility](#browser-compatibility)
99
- [Install](#install)
1010
- [Usage](#usage)
1111
- [CLI](#cli)
@@ -141,12 +141,14 @@ Table of content:
141141
- [isIDBSupported()](#isidbsupported)
142142
- [Development](#development)
143143

144-
## Browser support
144+
## Browser compatibility
145145

146-
| |manifest v2 |manifest v3 |
147-
|----------------|--------------|-------------|
148-
| Chrome || 🚧 |
149-
| Firefox || 🚧 |
146+
| Browser | Version |
147+
|-----------------------------|---------|
148+
| Chromium-based browsers MV2 | 79 |
149+
| Chromium-based browsers MV3 | 121 |
150+
| Firefox | 78 |
151+
| Firefox Mobile | 113 |
150152

151153
## Install
152154

@@ -202,9 +204,9 @@ Commands:
202204

203205
## Side effects
204206

205-
### Side Effects
206-
In this project, the `sideEffects` field is defined as follows:
207-
```
207+
In this project, the `sideEffects` field is defined as follows in the `package.json` file:
208+
209+
```json
208210
"sideEffects": [
209211
"dist/assistant-inject.js",
210212
"dist/content-script.js",

packages/tswebextension/src/lib/common/constants.ts

+5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ export const BACKGROUND_TAB_ID = -1;
3030
*/
3131
export const LF = '\n';
3232

33+
/**
34+
* Semicolon character.
35+
*/
36+
export const SEMICOLON = ';';
37+
3338
/**
3439
* Timeout used for deletion of request context data and frame context data from the storage.
3540
*/

packages/tswebextension/src/lib/common/content-script/css-hits-counter.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { type IAffectedElement } from '@adguard/extended-css';
22

3+
import { SEMICOLON } from '../constants';
4+
35
import { HitsStorage } from './hits-storage';
46
import { type RuleInfo } from './rule-info';
57
import { ElementUtils } from './utils/element-utils';
@@ -258,7 +260,6 @@ export class CssHitsCounter {
258260
start: number,
259261
length: number | null,
260262
): ICountedElement[] {
261-
const RULE_FILTER_SEPARATOR = ';';
262263
start = start || 0;
263264
length = length || elements.length;
264265

@@ -271,7 +272,7 @@ export class CssHitsCounter {
271272
}
272273

273274
const { filterId, ruleIndex } = cssHitData;
274-
const ruleAndFilterString = filterId + RULE_FILTER_SEPARATOR + ruleIndex;
275+
const ruleAndFilterString = filterId + SEMICOLON + ruleIndex;
275276

276277
if (this.hitsStorage.isCounted(element, ruleAndFilterString)) {
277278
continue;

packages/tswebextension/src/lib/common/cosmetic-api.ts

+245-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
import { CosmeticRuleType } from '@adguard/agtree';
12
import { type CosmeticResult, type CosmeticRule } from '@adguard/tsurlfilter';
23

4+
import { LF, SEMICOLON } from './constants';
5+
import { defaultFilteringLog, FilteringEventType } from './filtering-log';
36
import { type ContentType } from './request-type';
7+
import { getDomain } from './utils/url';
8+
import { nanoid } from './utils/nanoid';
49

510
/**
611
* Information for logging js rules.
@@ -76,13 +81,18 @@ export class CosmeticApiCommon {
7681
/**
7782
* Separator for hit stats.
7883
*/
79-
protected static readonly HIT_SEP = encodeURIComponent(';');
84+
protected static readonly HIT_SEP = encodeURIComponent(SEMICOLON);
8085

8186
/**
8287
* Element hiding CSS style ending.
8388
*/
8489
protected static readonly HIT_END = "' !important; }";
8590

91+
/**
92+
* Regular expression to find content attribute in css rule.
93+
*/
94+
protected static CONTENT_ATTR_RE = /[{;"(]\s*content\s*:/gi;
95+
8696
/**
8797
* Builds stylesheets from rules.
8898
* If `groupElemhideSelectors` is set,
@@ -163,4 +173,238 @@ export class CosmeticApiCommon {
163173
}
164174
return elemhideStyles;
165175
}
176+
177+
/**
178+
* Patches rule selector adding adguard mark rule info in the content attribute.
179+
*
180+
* @example
181+
* `.selector` -> `.selector { content: 'adguard{filterId};{ruleText} !important;}`
182+
*
183+
* @param rule Elemhide cosmetic rule.
184+
*
185+
* @returns Rule with modified stylesheet, containing content marker.
186+
*/
187+
private static addMarkerToElemhideRule(rule: CosmeticRule): string {
188+
const result: string[] = [];
189+
result.push(rule.getContent());
190+
result.push(CosmeticApiCommon.ELEMHIDE_HIT_START);
191+
result.push(String(rule.getFilterListId()));
192+
result.push(CosmeticApiCommon.HIT_SEP);
193+
result.push(String(rule.getIndex()));
194+
result.push(CosmeticApiCommon.HIT_END);
195+
return result.join('');
196+
}
197+
198+
/**
199+
* Patches rule selector adding adguard mark and rule info in the content style attribute.
200+
* Example:
201+
* .selector { color: red } -> .selector { color: red, content: 'adguard{filterId};{ruleText} !important;}.
202+
*
203+
* @param rule Inject cosmetic rule.
204+
*
205+
* @returns Modified rule with injected content marker into stylesheet.
206+
*/
207+
private static addMarkerToInjectRule(rule: CosmeticRule): string {
208+
const result: string[] = [];
209+
const ruleContent = rule.getContent();
210+
// if rule text has content attribute we don't add rule marker
211+
if (CosmeticApiCommon.CONTENT_ATTR_RE.test(ruleContent)) {
212+
return ruleContent;
213+
}
214+
215+
// remove closing brace
216+
const ruleTextWithoutCloseBrace = ruleContent.slice(0, -1).trim();
217+
// check semicolon
218+
const ruleTextWithSemicolon = ruleTextWithoutCloseBrace.endsWith(SEMICOLON)
219+
? ruleTextWithoutCloseBrace
220+
: `${ruleTextWithoutCloseBrace}${SEMICOLON}`;
221+
result.push(ruleTextWithSemicolon);
222+
result.push(CosmeticApiCommon.INJECT_HIT_START);
223+
result.push(String(rule.getFilterListId()));
224+
result.push(CosmeticApiCommon.HIT_SEP);
225+
result.push(String(rule.getIndex()));
226+
result.push(CosmeticApiCommon.HIT_END);
227+
228+
return result.join('');
229+
}
230+
231+
/**
232+
* Builds stylesheets with css-hits marker.
233+
*
234+
* @param elemhideRules Elemhide css rules.
235+
* @param injectRules Inject css rules.
236+
*
237+
* @returns List of stylesheet expressions.
238+
*/
239+
private static buildStyleSheetsWithHits(
240+
elemhideRules: CosmeticRule[],
241+
injectRules: CosmeticRule[],
242+
): string[] {
243+
const elemhideStyles = elemhideRules.map((x) => CosmeticApiCommon.addMarkerToElemhideRule(x));
244+
const injectStyles = injectRules.map((x) => CosmeticApiCommon.addMarkerToInjectRule(x));
245+
246+
return [...elemhideStyles, ...injectStyles];
247+
}
248+
249+
/**
250+
* Builds extended css rules from cosmetic result.
251+
*
252+
* @param cosmeticResult Cosmetic result.
253+
* @param collectingCosmeticRulesHits Flag to collect cosmetic rules hits.
254+
* @returns Array of extended css rules or null.
255+
*/
256+
public static getExtCssRules(
257+
cosmeticResult: CosmeticResult,
258+
collectingCosmeticRulesHits = false,
259+
): string[] | null {
260+
const { elementHiding, CSS } = cosmeticResult;
261+
262+
const elemhideExtCss = elementHiding.genericExtCss.concat(elementHiding.specificExtCss);
263+
const injectExtCss = CSS.genericExtCss.concat(CSS.specificExtCss);
264+
265+
let extCssRules: string[];
266+
267+
if (collectingCosmeticRulesHits) {
268+
extCssRules = CosmeticApiCommon.buildStyleSheetsWithHits(elemhideExtCss, injectExtCss);
269+
} else {
270+
extCssRules = CosmeticApiCommon.buildStyleSheets(elemhideExtCss, injectExtCss, false);
271+
}
272+
273+
return extCssRules.length > 0
274+
? extCssRules
275+
: null;
276+
}
277+
278+
/**
279+
* Retrieves CSS styles from the cosmetic result.
280+
*
281+
* @param cosmeticResult Cosmetic result.
282+
* @param collectingCosmeticRulesHits Flag to collect cosmetic rules hits.
283+
*
284+
* @returns Css styles as string, or `undefined` if no styles found.
285+
*/
286+
public static getCssText(
287+
cosmeticResult: CosmeticResult,
288+
collectingCosmeticRulesHits = false,
289+
): string | undefined {
290+
const { elementHiding, CSS } = cosmeticResult;
291+
292+
const elemhideCss = elementHiding.generic.concat(elementHiding.specific);
293+
const injectCss = CSS.generic.concat(CSS.specific);
294+
295+
let styles: string[];
296+
297+
if (collectingCosmeticRulesHits) {
298+
styles = CosmeticApiCommon.buildStyleSheetsWithHits(elemhideCss, injectCss);
299+
} else {
300+
styles = CosmeticApiCommon.buildStyleSheets(elemhideCss, injectCss, true);
301+
}
302+
303+
if (styles.length > 0) {
304+
return styles.join(CosmeticApiCommon.LINE_BREAK);
305+
}
306+
307+
return undefined;
308+
}
309+
310+
/**
311+
* Wraps the given JavaScript code in a self-invoking function for safe execution
312+
* and appends a source URL comment for debugging purposes.
313+
*
314+
* @param scriptText The JavaScript code to wrap.
315+
* @returns The wrapped script code, or an empty string if the input is falsy.
316+
*/
317+
protected static wrapScriptText(scriptText: string): string {
318+
if (!scriptText) {
319+
return '';
320+
}
321+
322+
// The "//# sourceURL=ag-scripts.js" line is necessary to ensure the script always has the same URL,
323+
// making it possible to debug consistently.
324+
return `
325+
(function () {
326+
try {
327+
${scriptText}
328+
} catch (ex) {
329+
console.error('Error executing AG js: ' + ex);
330+
}
331+
})();
332+
//# sourceURL=ag-scripts.js
333+
`;
334+
}
335+
336+
/**
337+
* Combines unique script strings into a single script text.
338+
*
339+
* Script string is being trimmed and a semicolon is added if it is missing.
340+
*
341+
* @param uniqueScriptStrings Set of unique script strings to combine.
342+
*
343+
* @returns Combined script string.
344+
*/
345+
protected static combineScripts(uniqueScriptStrings: Set<string>): string {
346+
let scriptText = '';
347+
348+
uniqueScriptStrings.forEach((rawScriptStr) => {
349+
const script = rawScriptStr.trim();
350+
351+
scriptText += script.endsWith(SEMICOLON)
352+
? `${script}${LF}`
353+
: `${script}${SEMICOLON}${LF}`;
354+
});
355+
356+
return scriptText;
357+
}
358+
359+
/**
360+
* Logs js rules applied to a specific frame.
361+
*
362+
* @param params Data for js rule logging.
363+
* @param predicate Function to filter script rules before logging, e.g. check if the script rule is local.
364+
*/
365+
protected static logScriptRules(
366+
params: LogJsRulesParams,
367+
predicate: (cosmeticResult: CosmeticRule) => boolean,
368+
): void {
369+
const {
370+
tabId,
371+
cosmeticResult,
372+
url,
373+
contentType,
374+
timestamp,
375+
} = params;
376+
377+
const scriptRules = cosmeticResult.getScriptRules().filter(predicate);
378+
379+
for (const scriptRule of scriptRules) {
380+
if (scriptRule.isGeneric()) {
381+
continue;
382+
}
383+
384+
const ruleType = scriptRule.getType();
385+
defaultFilteringLog.publishEvent({
386+
type: FilteringEventType.JsInject,
387+
data: {
388+
script: true,
389+
tabId,
390+
// for proper filtering log request info rule displaying
391+
// event id should be unique for each event, not copied from request
392+
// https://github.com/AdguardTeam/AdguardBrowserExtension/issues/2341
393+
eventId: nanoid(),
394+
requestUrl: url,
395+
frameUrl: url,
396+
frameDomain: getDomain(url) as string,
397+
requestType: contentType,
398+
timestamp,
399+
filterId: scriptRule.getFilterListId(),
400+
ruleIndex: scriptRule.getIndex(),
401+
cssRule: ruleType === CosmeticRuleType.ElementHidingRule
402+
|| ruleType === CosmeticRuleType.CssInjectionRule,
403+
scriptRule: ruleType === CosmeticRuleType.ScriptletInjectionRule
404+
|| ruleType === CosmeticRuleType.JsInjectionRule,
405+
contentRule: ruleType === CosmeticRuleType.HtmlFilteringRule,
406+
},
407+
});
408+
}
409+
}
166410
}

0 commit comments

Comments
 (0)