@adguard/agtree 1.1.5 → 1.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /*
2
- * AGTree v1.1.5 (build date: Tue, 05 Sep 2023 15:10:53 GMT)
2
+ * AGTree v1.1.7 (build date: Tue, 07 Nov 2023 14:17:46 GMT)
3
3
  * (c) 2023 AdGuard Software Ltd.
4
4
  * Released under the MIT license
5
5
  * https://github.com/AdguardTeam/tsurlfilter/tree/master/packages/agtree#readme
@@ -7,7 +7,7 @@
7
7
  import valid from 'semver/functions/valid.js';
8
8
  import coerce from 'semver/functions/coerce.js';
9
9
  import JSON5 from 'json5';
10
- import { walk, parse, toPlainObject, find, generate, fromPlainObject, List } from '@adguard/ecss-tree';
10
+ import { walk, parse, toPlainObject, find, generate, List, fromPlainObject } from '@adguard/ecss-tree';
11
11
  import * as ecssTree from '@adguard/ecss-tree';
12
12
  export { ecssTree as ECSSTree };
13
13
  import cloneDeep from 'clone-deep';
@@ -262,7 +262,7 @@ const NEGATION_MARKER = '~';
262
262
  /**
263
263
  * The wildcard symbol — `*`.
264
264
  */
265
- const WILDCARD$1 = ASTERISK;
265
+ const WILDCARD = ASTERISK;
266
266
  /**
267
267
  * Classic domain separator.
268
268
  *
@@ -2861,7 +2861,7 @@ class ModifierParser {
2861
2861
  const modifierEnd = Math.max(StringUtils.skipWSBack(raw) + 1, modifierNameStart);
2862
2862
  // Modifier name can't be empty
2863
2863
  if (modifierNameStart === modifierEnd) {
2864
- throw new AdblockSyntaxError('Modifier name can\'t be empty', locRange(loc, 0, raw.length));
2864
+ throw new AdblockSyntaxError('Modifier name cannot be empty', locRange(loc, 0, raw.length));
2865
2865
  }
2866
2866
  let modifier;
2867
2867
  let value;
@@ -2885,7 +2885,7 @@ class ModifierParser {
2885
2885
  };
2886
2886
  // Value can't be empty
2887
2887
  if (assignmentIndex + 1 === modifierEnd) {
2888
- throw new AdblockSyntaxError('Modifier value can\'t be empty', locRange(loc, 0, raw.length));
2888
+ throw new AdblockSyntaxError('Modifier value cannot be empty', locRange(loc, 0, raw.length));
2889
2889
  }
2890
2890
  // Skip whitespace after the assignment operator
2891
2891
  const valueStart = StringUtils.skipWS(raw, assignmentIndex + MODIFIER_ASSIGN_OPERATOR.length);
@@ -3183,8 +3183,29 @@ const FORBIDDEN_CSS_FUNCTIONS = new Set([
3183
3183
  'url',
3184
3184
  ]);
3185
3185
 
3186
+ /**
3187
+ * @file Clone related utilities
3188
+ *
3189
+ * We should keep clone related functions in this file. Thus, we just provide
3190
+ * a simple interface for cloning values, we use it across the AGTree project,
3191
+ * and the implementation "under the hood" can be improved later, if needed.
3192
+ */
3193
+ /**
3194
+ * Clones an input value to avoid side effects. Use it only in justified cases,
3195
+ * because it can impact performance negatively.
3196
+ *
3197
+ * @param value Value to clone
3198
+ * @returns Cloned value
3199
+ */
3200
+ function clone(value) {
3201
+ // TODO: Replace cloneDeep with a more efficient implementation
3202
+ return cloneDeep(value);
3203
+ }
3204
+
3186
3205
  /**
3187
3206
  * @file Additional / helper functions for ECSSTree / CSSTree.
3207
+ *
3208
+ * @note There are no tests for some functions, but during the AGTree optimization we remove them anyway.
3188
3209
  */
3189
3210
  /**
3190
3211
  * Common CSSTree parsing options.
@@ -3320,10 +3341,10 @@ class CssTree {
3320
3341
  ast = CssTree.parse(selectorList, CssTreeParserContext.selectorList);
3321
3342
  }
3322
3343
  else {
3323
- ast = cloneDeep(selectorList);
3344
+ ast = clone(selectorList);
3324
3345
  }
3325
3346
  const nodes = [];
3326
- // TODO: CSSTree types should be improved, as a workaround we use `any` here
3347
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
3327
3348
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
3328
3349
  walk(ast, (node) => {
3329
3350
  if (CssTree.isExtendedCssNode(node, pseudoClasses, attributeSelectors)) {
@@ -3352,9 +3373,9 @@ class CssTree {
3352
3373
  ast = CssTree.parse(selectorList, CssTreeParserContext.selectorList);
3353
3374
  }
3354
3375
  else {
3355
- ast = cloneDeep(selectorList);
3376
+ ast = selectorList;
3356
3377
  }
3357
- // TODO: CSSTree types should be improved, as a workaround we use `any` here
3378
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
3358
3379
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
3359
3380
  return find(ast, (node) => CssTree.isExtendedCssNode(node, pseudoClasses, attributeSelectors)) !== null;
3360
3381
  }
@@ -3391,14 +3412,14 @@ class CssTree {
3391
3412
  ast = CssTree.parse(declarationList, CssTreeParserContext.declarationList);
3392
3413
  }
3393
3414
  else {
3394
- ast = cloneDeep(declarationList);
3415
+ ast = clone(declarationList);
3395
3416
  }
3396
3417
  const nodes = [];
3397
3418
  // While walking the AST we should skip the nested functions,
3398
3419
  // for example skip url()s in cross-fade(url(), url()), since
3399
3420
  // cross-fade() itself is already a forbidden function
3400
3421
  let inForbiddenFunction = false;
3401
- // TODO: CSSTree types should be improved, as a workaround we use `any` here
3422
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
3402
3423
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
3403
3424
  walk(ast, {
3404
3425
  enter: (node) => {
@@ -3436,9 +3457,9 @@ class CssTree {
3436
3457
  ast = CssTree.parse(declarationList, CssTreeParserContext.declarationList);
3437
3458
  }
3438
3459
  else {
3439
- ast = cloneDeep(declarationList);
3460
+ ast = clone(declarationList);
3440
3461
  }
3441
- // TODO: CSSTree types should be improved, as a workaround we use `any` here
3462
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
3442
3463
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
3443
3464
  return find(ast, (node) => CssTree.isForbiddenFunction(node, forbiddenFunctions)) !== null;
3444
3465
  }
@@ -3670,6 +3691,180 @@ class CssTree {
3670
3691
  });
3671
3692
  return result.trim();
3672
3693
  }
3694
+ /**
3695
+ * Generates string representation of the selector list.
3696
+ *
3697
+ * @param ast SelectorList AST
3698
+ * @returns String representation of the selector list
3699
+ */
3700
+ static generateSelectorListPlain(ast) {
3701
+ const result = [];
3702
+ if (!ast.children || ast.children.length === 0) {
3703
+ throw new Error('Selector list cannot be empty');
3704
+ }
3705
+ ast.children.forEach((selector, index, nodeList) => {
3706
+ if (selector.type !== CssTreeNodeType.Selector) {
3707
+ throw new Error(`Unexpected node type: ${selector.type}`);
3708
+ }
3709
+ result.push(this.generateSelectorPlain(selector));
3710
+ // If there is a next node, add a comma and a space after the selector
3711
+ if (nodeList[index + 1]) {
3712
+ result.push(COMMA, SPACE);
3713
+ }
3714
+ });
3715
+ return result.join(EMPTY);
3716
+ }
3717
+ /**
3718
+ * Selector generation based on CSSTree's AST. This is necessary because CSSTree
3719
+ * only adds spaces in some edge cases.
3720
+ *
3721
+ * @param ast CSS Tree AST
3722
+ * @returns CSS selector as string
3723
+ */
3724
+ static generateSelectorPlain(ast) {
3725
+ let result = EMPTY;
3726
+ let inAttributeSelector = false;
3727
+ let depth = 0;
3728
+ let selectorListDepth = -1;
3729
+ let prevNode = ast;
3730
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
3731
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3732
+ walk(ast, {
3733
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
3734
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3735
+ enter: (node) => {
3736
+ depth += 1;
3737
+ // Skip attribute selector / selector list children
3738
+ if (inAttributeSelector || selectorListDepth > -1) {
3739
+ return;
3740
+ }
3741
+ switch (node.type) {
3742
+ // "Trivial" nodes
3743
+ case CssTreeNodeType.TypeSelector:
3744
+ result += node.name;
3745
+ break;
3746
+ case CssTreeNodeType.ClassSelector:
3747
+ result += DOT;
3748
+ result += node.name;
3749
+ break;
3750
+ case CssTreeNodeType.IdSelector:
3751
+ result += HASHMARK;
3752
+ result += node.name;
3753
+ break;
3754
+ case CssTreeNodeType.Identifier:
3755
+ result += node.name;
3756
+ break;
3757
+ case CssTreeNodeType.Raw:
3758
+ result += node.value;
3759
+ break;
3760
+ // "Advanced" nodes
3761
+ case CssTreeNodeType.Nth:
3762
+ // Default generation enough
3763
+ result += generate(node);
3764
+ break;
3765
+ // For example :not([id], [name])
3766
+ case CssTreeNodeType.SelectorList:
3767
+ // eslint-disable-next-line no-case-declarations
3768
+ const selectors = [];
3769
+ node.children.forEach((selector) => {
3770
+ if (selector.type === CssTreeNodeType.Selector) {
3771
+ selectors.push(CssTree.generateSelectorPlain(selector));
3772
+ }
3773
+ else if (selector.type === CssTreeNodeType.Raw) {
3774
+ selectors.push(selector.value);
3775
+ }
3776
+ });
3777
+ // Join selector lists
3778
+ result += selectors.join(COMMA + SPACE);
3779
+ // Skip nodes here
3780
+ selectorListDepth = depth;
3781
+ break;
3782
+ case CssTreeNodeType.Combinator:
3783
+ if (node.name === SPACE) {
3784
+ result += node.name;
3785
+ break;
3786
+ }
3787
+ // Prevent this case (unnecessary space): has( > .something)
3788
+ if (prevNode.type !== CssTreeNodeType.Selector) {
3789
+ result += SPACE;
3790
+ }
3791
+ result += node.name;
3792
+ result += SPACE;
3793
+ break;
3794
+ case CssTreeNodeType.AttributeSelector:
3795
+ result += OPEN_SQUARE_BRACKET;
3796
+ // Identifier name
3797
+ if (node.name) {
3798
+ result += node.name.name;
3799
+ }
3800
+ // Matcher operator, eg =
3801
+ if (node.matcher) {
3802
+ result += node.matcher;
3803
+ // Value can be String, Identifier or null
3804
+ if (node.value !== null) {
3805
+ // String node
3806
+ if (node.value.type === CssTreeNodeType.String) {
3807
+ result += generate(node.value);
3808
+ }
3809
+ else if (node.value.type === CssTreeNodeType.Identifier) {
3810
+ // Identifier node
3811
+ result += node.value.name;
3812
+ }
3813
+ }
3814
+ }
3815
+ // Flags
3816
+ if (node.flags) {
3817
+ // Space before flags
3818
+ result += SPACE;
3819
+ result += node.flags;
3820
+ }
3821
+ result += CLOSE_SQUARE_BRACKET;
3822
+ inAttributeSelector = true;
3823
+ break;
3824
+ case CssTreeNodeType.PseudoElementSelector:
3825
+ result += COLON;
3826
+ result += COLON;
3827
+ result += node.name;
3828
+ if (node.children !== null) {
3829
+ result += OPEN_PARENTHESIS;
3830
+ }
3831
+ break;
3832
+ case CssTreeNodeType.PseudoClassSelector:
3833
+ result += COLON;
3834
+ result += node.name;
3835
+ if (node.children !== null) {
3836
+ result += OPEN_PARENTHESIS;
3837
+ }
3838
+ break;
3839
+ }
3840
+ prevNode = node;
3841
+ },
3842
+ leave: (node) => {
3843
+ depth -= 1;
3844
+ if (node.type === CssTreeNodeType.SelectorList && depth + 1 === selectorListDepth) {
3845
+ selectorListDepth = -1;
3846
+ }
3847
+ if (selectorListDepth > -1) {
3848
+ return;
3849
+ }
3850
+ if (node.type === CssTreeNodeType.AttributeSelector) {
3851
+ inAttributeSelector = false;
3852
+ }
3853
+ if (inAttributeSelector) {
3854
+ return;
3855
+ }
3856
+ switch (node.type) {
3857
+ case CssTreeNodeType.PseudoElementSelector:
3858
+ case CssTreeNodeType.PseudoClassSelector:
3859
+ if (node.children) {
3860
+ result += CLOSE_PARENTHESIS;
3861
+ }
3862
+ break;
3863
+ }
3864
+ },
3865
+ });
3866
+ return result.trim();
3867
+ }
3673
3868
  /**
3674
3869
  * Block generation based on CSSTree's AST. This is necessary because CSSTree only adds spaces in some edge cases.
3675
3870
  *
@@ -3853,6 +4048,29 @@ class CssTree {
3853
4048
  });
3854
4049
  return result;
3855
4050
  }
4051
+ /**
4052
+ * Helper function to generate a raw string from a function selector's children
4053
+ *
4054
+ * @param node Function node
4055
+ * @returns Generated function value
4056
+ * @example `responseheader(name)` -> `name`
4057
+ */
4058
+ static generateFunctionPlainValue(node) {
4059
+ const result = [];
4060
+ node.children?.forEach((child) => {
4061
+ switch (child.type) {
4062
+ case CssTreeNodeType.Raw:
4063
+ result.push(child.value);
4064
+ break;
4065
+ default:
4066
+ // Fallback to CSSTree's default generate function
4067
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
4068
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4069
+ result.push(generate(child));
4070
+ }
4071
+ });
4072
+ return result.join(EMPTY);
4073
+ }
3856
4074
  }
3857
4075
 
3858
4076
  /**
@@ -3900,7 +4118,7 @@ class ElementHidingBodyParser {
3900
4118
  * @throws If the AST is invalid
3901
4119
  */
3902
4120
  static generate(ast) {
3903
- return CssTree.generateSelectorList(fromPlainObject(ast.selectorList));
4121
+ return CssTree.generateSelectorListPlain(ast.selectorList);
3904
4122
  }
3905
4123
  }
3906
4124
 
@@ -4144,7 +4362,7 @@ class CssInjectionBodyParser {
4144
4362
  if (mediaQueryList || declarationList || remove) {
4145
4363
  throw new AdblockSyntaxError(
4146
4364
  // eslint-disable-next-line max-len
4147
- 'Invalid selector, regular selector elements can\'t be used after special pseudo-classes', {
4365
+ 'Invalid selector, regular selector elements cannot be used after special pseudo-classes', {
4148
4366
  start: node.loc?.start ?? loc,
4149
4367
  end: shiftLoc(loc, raw.length),
4150
4368
  });
@@ -4833,7 +5051,7 @@ function createModifierListNode(modifiers = []) {
4833
5051
  const result = {
4834
5052
  type: 'ModifierList',
4835
5053
  // We need to clone the modifiers to avoid side effects
4836
- children: cloneDeep(modifiers),
5054
+ children: modifiers.length ? clone(modifiers) : [],
4837
5055
  };
4838
5056
  return result;
4839
5057
  }
@@ -4873,8 +5091,9 @@ function hasUboModifierIndicator(rawSelectorList) {
4873
5091
  * @returns Linked list based selector
4874
5092
  */
4875
5093
  function convertSelectorToLinkedList(selector) {
5094
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
4876
5095
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4877
- return fromPlainObject(cloneDeep(selector));
5096
+ return fromPlainObject(clone(selector));
4878
5097
  }
4879
5098
  /**
4880
5099
  * Helper function that always returns the linked list version of the
@@ -4884,8 +5103,9 @@ function convertSelectorToLinkedList(selector) {
4884
5103
  * @returns Linked list based selector list
4885
5104
  */
4886
5105
  function convertSelectorListToLinkedList(selectorList) {
5106
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
4887
5107
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4888
- return fromPlainObject(cloneDeep(selectorList));
5108
+ return fromPlainObject(clone(selectorList));
4889
5109
  }
4890
5110
  /**
4891
5111
  * Helper function for checking and removing bounding combinators
@@ -5970,7 +6190,8 @@ class FilterListParser {
5970
6190
  */
5971
6191
  static generate(ast, preferRaw = false) {
5972
6192
  let result = EMPTY;
5973
- for (const rule of ast.children) {
6193
+ for (let i = 0; i < ast.children.length; i += 1) {
6194
+ const rule = ast.children[i];
5974
6195
  if (preferRaw && rule.raws?.text) {
5975
6196
  result += rule.raws.text;
5976
6197
  }
@@ -5987,6 +6208,11 @@ class FilterListParser {
5987
6208
  case 'lf':
5988
6209
  result += LF;
5989
6210
  break;
6211
+ default:
6212
+ if (i !== ast.children.length - 1) {
6213
+ result += LF;
6214
+ }
6215
+ break;
5990
6216
  }
5991
6217
  }
5992
6218
  return result;
@@ -6036,7 +6262,7 @@ class RuleConversionError extends Error {
6036
6262
  }
6037
6263
  }
6038
6264
 
6039
- var data$T = { adg_os_any:{ name:"all",
6265
+ var data$U = { adg_os_any:{ name:"all",
6040
6266
  description:"$all modifier is made of $document, $popup, and all content-type modifiers combined.",
6041
6267
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#all-modifier",
6042
6268
  negatable:false,
@@ -6062,14 +6288,14 @@ var data$T = { adg_os_any:{ name:"all",
6062
6288
  negatable:false,
6063
6289
  block_only:false } };
6064
6290
 
6065
- var data$S = { adg_os_any:{ name:"app",
6291
+ var data$T = { adg_os_any:{ name:"app",
6066
6292
  description:"The `$app` modifier lets you narrow the rule coverage down to a specific application or a list of applications.\nThe modifier's behavior and syntax perfectly match the corresponding basic rules `$app` modifier.",
6067
6293
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#app-modifier",
6068
6294
  assignable:true,
6069
6295
  negatable:false,
6070
6296
  value_format:"pipe_separated_apps" } };
6071
6297
 
6072
- var data$R = { adg_os_any:{ name:"badfilter",
6298
+ var data$S = { adg_os_any:{ name:"badfilter",
6073
6299
  description:"The rules with the `$badfilter` modifier disable other basic rules to which they refer. It means that\nthe text of the disabled rule should match the text of the `$badfilter` rule (without the `$badfilter` modifier).",
6074
6300
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#badfilter-modifier",
6075
6301
  negatable:false },
@@ -6090,19 +6316,19 @@ var data$R = { adg_os_any:{ name:"badfilter",
6090
6316
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#badfilter",
6091
6317
  negatable:false } };
6092
6318
 
6093
- var data$Q = { ubo_ext_any:{ name:"cname",
6319
+ var data$R = { ubo_ext_any:{ name:"cname",
6094
6320
  description:"When used in an exception filter,\nit will bypass blocking CNAME uncloaked requests for the current (specified) document.",
6095
6321
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#cname",
6096
6322
  negatable:false,
6097
6323
  exception_only:true } };
6098
6324
 
6099
- var data$P = { adg_os_any:{ name:"content",
6325
+ var data$Q = { adg_os_any:{ name:"content",
6100
6326
  description:"Disables HTML filtering and `$replace` rules on the pages that match the rule.",
6101
6327
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#content-modifier",
6102
6328
  negatable:false,
6103
6329
  exception_only:true } };
6104
6330
 
6105
- var data$O = { adg_os_any:{ name:"cookie",
6331
+ var data$P = { adg_os_any:{ name:"cookie",
6106
6332
  description:"The `$cookie` modifier completely changes rule behavior.\nInstead of blocking a request, this modifier makes us suppress or modify the Cookie and Set-Cookie headers.",
6107
6333
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#cookie-modifier",
6108
6334
  assignable:true,
@@ -6117,7 +6343,7 @@ var data$O = { adg_os_any:{ name:"cookie",
6117
6343
  value_optional:true,
6118
6344
  value_format:"^([^;=\\s]*?)((?:;(maxAge=\\d+;?)?|(sameSite=(lax|none|strict);?)?){1,3})(?<!;)$" } };
6119
6345
 
6120
- var data$N = { adg_os_any:{ name:"csp",
6346
+ var data$O = { adg_os_any:{ name:"csp",
6121
6347
  description:"This modifier completely changes the rule behavior.\nIf it is applied to a rule, it will not block the matching request.\nThe response headers are going to be modified instead.",
6122
6348
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#csp-modifier",
6123
6349
  conflicts:[ "domain",
@@ -6164,7 +6390,7 @@ var data$N = { adg_os_any:{ name:"csp",
6164
6390
  value_optional:true,
6165
6391
  value_format:"csp_value" } };
6166
6392
 
6167
- var data$M = { adg_os_any:{ name:"denyallow",
6393
+ var data$N = { adg_os_any:{ name:"denyallow",
6168
6394
  description:"The `$denyallow` modifier allows to avoid creating additional rules\nwhen it is needed to disable a certain rule for specific domains.\n`$denyallow` matches only target domains and not referrer domains.",
6169
6395
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#denyallow-modifier",
6170
6396
  conflicts:[ "to" ],
@@ -6200,7 +6426,7 @@ var data$M = { adg_os_any:{ name:"denyallow",
6200
6426
  negatable:false,
6201
6427
  value_format:"pipe_separated_denyallow_domains" } };
6202
6428
 
6203
- var data$L = { adg_os_any:{ name:"document",
6429
+ var data$M = { adg_os_any:{ name:"document",
6204
6430
  description:"The rule corresponds to the main frame document requests,\ni.e. HTML documents that are loaded in the browser tab.",
6205
6431
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#document-modifier",
6206
6432
  negatable:false },
@@ -6226,7 +6452,7 @@ var data$L = { adg_os_any:{ name:"document",
6226
6452
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#document",
6227
6453
  negatable:false } };
6228
6454
 
6229
- var data$K = { adg_any:{ name:"domain",
6455
+ var data$L = { adg_any:{ name:"domain",
6230
6456
  aliases:[ "from" ],
6231
6457
  description:"The `$domain` modifier limits the rule application area to a list of domains and their subdomains.",
6232
6458
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#domain-modifier",
@@ -6247,7 +6473,7 @@ var data$K = { adg_any:{ name:"domain",
6247
6473
  negatable:false,
6248
6474
  value_format:"pipe_separated_domains" } };
6249
6475
 
6250
- var data$J = { adg_any:{ name:"elemhide",
6476
+ var data$K = { adg_any:{ name:"elemhide",
6251
6477
  aliases:[ "ehide" ],
6252
6478
  description:"Disables any cosmetic rules on the pages matching the rule.",
6253
6479
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#elemhide-modifier",
@@ -6266,7 +6492,7 @@ var data$J = { adg_any:{ name:"elemhide",
6266
6492
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#elemhide-1",
6267
6493
  description:"Disables any cosmetic rules on the pages matching the rule." } };
6268
6494
 
6269
- var data$I = { adg_os_any:{ name:"empty",
6495
+ var data$J = { adg_os_any:{ name:"empty",
6270
6496
  description:"This modifier is deprecated in favor of the $redirect modifier.\nRules with `$empty` are still supported and being converted into `$redirect=nooptext` now\nbut the support shall be removed in the future.",
6271
6497
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#empty-modifier",
6272
6498
  deprecated:true,
@@ -6283,7 +6509,7 @@ var data$I = { adg_os_any:{ name:"empty",
6283
6509
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#empty",
6284
6510
  negatable:false } };
6285
6511
 
6286
- var data$H = { adg_any:{ name:"first-party",
6512
+ var data$I = { adg_any:{ name:"first-party",
6287
6513
  aliases:[ "1p",
6288
6514
  "~third-party" ],
6289
6515
  description:"A restriction of first-party requests. Equal to `~third-party`.",
@@ -6296,7 +6522,7 @@ var data$H = { adg_any:{ name:"first-party",
6296
6522
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#1p",
6297
6523
  negatable:false } };
6298
6524
 
6299
- var data$G = { adg_os_any:{ name:"extension",
6525
+ var data$H = { adg_os_any:{ name:"extension",
6300
6526
  description:"Disables all userscripts on the pages matching this rule.",
6301
6527
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#extension-modifier",
6302
6528
  conflicts:[ "domain",
@@ -6312,7 +6538,7 @@ var data$G = { adg_os_any:{ name:"extension",
6312
6538
  inverse_conflicts:true,
6313
6539
  exception_only:true } };
6314
6540
 
6315
- var data$F = { adg_any:{ name:"font",
6541
+ var data$G = { adg_any:{ name:"font",
6316
6542
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#font-modifier",
6317
6543
  description:"The rule corresponds to requests for fonts, e.g. `.woff` filename extension." },
6318
6544
  abp_any:{ name:"font",
@@ -6322,7 +6548,7 @@ var data$F = { adg_any:{ name:"font",
6322
6548
  docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293#options",
6323
6549
  description:"The rule corresponds to requests for fonts, e.g. `.woff` filename extension." } };
6324
6550
 
6325
- var data$E = { adg_os_any:{ name:"genericblock",
6551
+ var data$F = { adg_os_any:{ name:"genericblock",
6326
6552
  description:"Disables generic basic rules on pages that correspond to exception rule.",
6327
6553
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#genericblock-modifier",
6328
6554
  conflicts:[ "domain",
@@ -6378,7 +6604,7 @@ var data$E = { adg_os_any:{ name:"genericblock",
6378
6604
  negatable:false,
6379
6605
  exception_only:true } };
6380
6606
 
6381
- var data$D = { adg_any:{ name:"generichide",
6607
+ var data$E = { adg_any:{ name:"generichide",
6382
6608
  aliases:[ "ghide" ],
6383
6609
  description:"Disables all generic cosmetic rules.",
6384
6610
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#generichide-modifier",
@@ -6410,7 +6636,7 @@ var data$D = { adg_any:{ name:"generichide",
6410
6636
  negatable:false,
6411
6637
  exception_only:true } };
6412
6638
 
6413
- var data$C = { adg_os_any:{ name:"header",
6639
+ var data$D = { adg_os_any:{ name:"header",
6414
6640
  description:"The `$header` modifier allows matching the HTTP response\nhaving a specific header with (optionally) a specific value.",
6415
6641
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#header-modifier",
6416
6642
  assignable:true,
@@ -6426,7 +6652,7 @@ var data$C = { adg_os_any:{ name:"header",
6426
6652
  assignable:true,
6427
6653
  value_format:"(?xi)\n ^\n # header name\n [\\w-]+\n (\n :\n # header value: string or regexp\n (\\w+|\\/.+\\/)\n )?" } };
6428
6654
 
6429
- var data$B = { adg_os_any:{ name:"hls",
6655
+ var data$C = { adg_os_any:{ name:"hls",
6430
6656
  description:"The `$hls` rules modify the response of a matching request.\nThey are intended as a convenient way to remove segments from HLS playlists (RFC 8216).",
6431
6657
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#hls-modifier",
6432
6658
  version_added:"CoreLibs 1.10",
@@ -6442,7 +6668,7 @@ var data$B = { adg_os_any:{ name:"hls",
6442
6668
  value_optional:true,
6443
6669
  value_format:"(?xi)\n (\n # string pattern\n \\w+\n # or regexp pattern\n |\n # TODO: improve regexp pattern to invalidate unescaped `/`, `$`, and `,`\n \\/.+\\/\n # options\n ([ti]*)?\n )" } };
6444
6670
 
6445
- var data$A = { adg_any:{ name:"image",
6671
+ var data$B = { adg_any:{ name:"image",
6446
6672
  description:"The rule corresponds to images requests.",
6447
6673
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#image-modifier" },
6448
6674
  abp_any:{ name:"image",
@@ -6452,7 +6678,7 @@ var data$A = { adg_any:{ name:"image",
6452
6678
  description:"The rule corresponds to images requests.",
6453
6679
  docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293#options" } };
6454
6680
 
6455
- var data$z = { adg_any:{ name:"important",
6681
+ var data$A = { adg_any:{ name:"important",
6456
6682
  description:"The `$important` modifier applied to a rule increases its priority\nover any other rule without `$important` modifier. Even over basic exception rules.",
6457
6683
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#important-modifier",
6458
6684
  negatable:false },
@@ -6461,7 +6687,7 @@ var data$z = { adg_any:{ name:"important",
6461
6687
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#important",
6462
6688
  negatable:false } };
6463
6689
 
6464
- var data$y = { adg_os_any:{ name:"inline-font",
6690
+ var data$z = { adg_os_any:{ name:"inline-font",
6465
6691
  description:"The `$inline-font` modifier is a sort of a shortcut for $csp modifier with specific value.\nE.g. `||example.org^$inline-font` is converting into:\n```adblock\n||example.org^$csp=font-src 'self' 'unsafe-eval' http: https: data: blob: mediastream: filesystem:\n```",
6466
6692
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#inline-font-modifier" },
6467
6693
  adg_ext_any:{ name:"inline-font",
@@ -6471,7 +6697,7 @@ var data$y = { adg_os_any:{ name:"inline-font",
6471
6697
  description:"The `$inline-font` modifier is a sort of a shortcut for $csp modifier with specific value.\nE.g. `||example.org^$inline-font` is converting into:\n```adblock\n||example.org^$csp=font-src 'self' 'unsafe-eval' http: https: data: blob: mediastream: filesystem:\n```",
6472
6698
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#inline-font" } };
6473
6699
 
6474
- var data$x = { adg_os_any:{ name:"inline-script",
6700
+ var data$y = { adg_os_any:{ name:"inline-script",
6475
6701
  description:"The `$inline-script` modifier is a sort of a shortcut for $csp modifier with specific value.\nE.g. `||example.org^$inline-script` is converting into:\n```adblock\n||example.org^$csp=script-src 'self' 'unsafe-eval' http: https: data: blob: mediastream: filesystem:\n```",
6476
6702
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#inline-script-modifier" },
6477
6703
  adg_ext_any:{ name:"inline-script",
@@ -6481,7 +6707,7 @@ var data$x = { adg_os_any:{ name:"inline-script",
6481
6707
  description:"The `$inline-script` modifier is a sort of a shortcut for $csp modifier with specific value.\nE.g. `||example.org^$inline-script` is converting into:\n```adblock\n||example.org^$csp=script-src 'self' 'unsafe-eval' http: https: data: blob: mediastream: filesystem:\n```",
6482
6708
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#inline-script" } };
6483
6709
 
6484
- var data$w = { adg_os_any:{ name:"jsinject",
6710
+ var data$x = { adg_os_any:{ name:"jsinject",
6485
6711
  description:"Forbids adding of javascript code to the page.",
6486
6712
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#jsinject-modifier",
6487
6713
  conflicts:[ "domain",
@@ -6540,7 +6766,7 @@ var data$w = { adg_os_any:{ name:"jsinject",
6540
6766
  negatable:false,
6541
6767
  exception_only:true } };
6542
6768
 
6543
- var data$v = { adg_os_any:{ name:"jsonprune",
6769
+ var data$w = { adg_os_any:{ name:"jsonprune",
6544
6770
  description:"The `$jsonprune` rules modify the response to a matching request\nby removing JSON items that match a modified JSONPath expression.\nThey do not modify responses which are not valid JSON documents.",
6545
6771
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#jsonprune-modifier",
6546
6772
  assignable:true,
@@ -6548,7 +6774,7 @@ var data$v = { adg_os_any:{ name:"jsonprune",
6548
6774
  value_optional:true,
6549
6775
  value_format:"(?xi)\n ^\n # the expression always starts with a dollar sign (for root)\n # which should be escaped\n \\\\\n \\$\n \\.?\n # TODO: improve the expression to invalidate unescaped `$` and `,`\n .+\n $" } };
6550
6776
 
6551
- var data$u = { adg_any:{ name:"match-case",
6777
+ var data$v = { adg_any:{ name:"match-case",
6552
6778
  description:"This modifier defines a rule which applies only to addresses that match the case.\nDefault rules are case-insensitive.",
6553
6779
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#match-case-modifier" },
6554
6780
  abp_any:{ name:"match-case",
@@ -6558,7 +6784,7 @@ var data$u = { adg_any:{ name:"match-case",
6558
6784
  description:"This modifier defines a rule which applies only to addresses that match the case.\nDefault rules are case-insensitive.",
6559
6785
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#match-case" } };
6560
6786
 
6561
- var data$t = { adg_any:{ name:"media",
6787
+ var data$u = { adg_any:{ name:"media",
6562
6788
  description:"A restriction of third-party and own requests.\nA third-party request is a request from a different domain.\nFor example, a request to `example.org` from `domain.com` is a third-party request.",
6563
6789
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#media-modifier" },
6564
6790
  abp_any:{ name:"media",
@@ -6568,7 +6794,7 @@ var data$t = { adg_any:{ name:"media",
6568
6794
  description:"A restriction of third-party and own requests.\nA third-party request is a request from a different domain.\nFor example, a request to `example.org` from `domain.com` is a third-party request.",
6569
6795
  docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293#options" } };
6570
6796
 
6571
- var data$s = { adg_os_any:{ name:"method",
6797
+ var data$t = { adg_os_any:{ name:"method",
6572
6798
  description:"This modifier limits the rule scope to requests that use the specified set of HTTP methods.\nNegated methods are allowed.",
6573
6799
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#method-modifier",
6574
6800
  negatable:false,
@@ -6587,7 +6813,7 @@ var data$s = { adg_os_any:{ name:"method",
6587
6813
  assignable:true,
6588
6814
  value_format:"pipe_separated_methods" } };
6589
6815
 
6590
- var data$r = { adg_os_any:{ name:"mp4",
6816
+ var data$s = { adg_os_any:{ name:"mp4",
6591
6817
  description:"As a response to blocked request AdGuard returns a short video placeholder.\nRules with `$mp4` are still supported and being converted into `$redirect=noopmp4-1s` now\nbut the support shall be removed in the future.",
6592
6818
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#mp4-modifier",
6593
6819
  deprecated:true,
@@ -6604,7 +6830,7 @@ var data$r = { adg_os_any:{ name:"mp4",
6604
6830
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#mp4",
6605
6831
  negatable:false } };
6606
6832
 
6607
- var data$q = { adg_os_any:{ name:"network",
6833
+ var data$r = { adg_os_any:{ name:"network",
6608
6834
  description:"This is basically a Firewall-kind of rules allowing to fully block\nor unblock access to a specified remote address.",
6609
6835
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#network-modifier",
6610
6836
  conflicts:[ "app",
@@ -6612,7 +6838,7 @@ var data$q = { adg_os_any:{ name:"network",
6612
6838
  inverse_conflicts:true,
6613
6839
  negatable:false } };
6614
6840
 
6615
- var data$p = { adg_os_any:{ name:"_",
6841
+ var data$q = { adg_os_any:{ name:"_",
6616
6842
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#noop-modifier",
6617
6843
  description:"The noop modifier does nothing and can be used solely to increase rules' readability.\nIt consists of a sequence of underscore characters (_) of any length\nand can appear in a rule as many times as needed.",
6618
6844
  negatable:false },
@@ -6633,13 +6859,13 @@ var data$p = { adg_os_any:{ name:"_",
6633
6859
  description:"The noop modifier does nothing and can be used solely to increase rules' readability.\nIt consists of a sequence of underscore characters (_) of any length\nand can appear in a rule as many times as needed.",
6634
6860
  negatable:false } };
6635
6861
 
6636
- var data$o = { adg_any:{ name:"object-subrequest",
6862
+ var data$p = { adg_any:{ name:"object-subrequest",
6637
6863
  description:"The `$object-subrequest` modifier is removed and is no longer supported.\nRules with it are considered as invalid.\nThe rule corresponds to requests by browser plugins (it is usually Flash).",
6638
6864
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#object-subrequest-modifier",
6639
6865
  removed:true,
6640
6866
  removal_message:"The `$object-subrequest` modifier is removed and is no longer supported.\nRules with it are considered as invalid." } };
6641
6867
 
6642
- var data$n = { adg_any:{ name:"object",
6868
+ var data$o = { adg_any:{ name:"object",
6643
6869
  description:"The rule corresponds to browser plugins resources, e.g. Java or Flash",
6644
6870
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#object-modifier" },
6645
6871
  abp_any:{ name:"object",
@@ -6649,7 +6875,7 @@ var data$n = { adg_any:{ name:"object",
6649
6875
  description:"The rule corresponds to browser plugins resources, e.g. Java or Flash.",
6650
6876
  docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" } };
6651
6877
 
6652
- var data$m = { adg_any:{ name:"other",
6878
+ var data$n = { adg_any:{ name:"other",
6653
6879
  description:"The rule applies to requests for which the type has not been determined\nor does not match the types listed above.",
6654
6880
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#other-modifier" },
6655
6881
  abp_any:{ name:"other",
@@ -6659,7 +6885,7 @@ var data$m = { adg_any:{ name:"other",
6659
6885
  description:"The rule applies to requests for which the type has not been determined\nor does not match the types listed above.",
6660
6886
  docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" } };
6661
6887
 
6662
- var data$l = { adg_os_any:{ name:"permissions",
6888
+ var data$m = { adg_os_any:{ name:"permissions",
6663
6889
  description:"For the requests matching a `$permissions` rule, ad blocker strengthens response's feature policy\nby adding additional feature policy equal to the `$permissions` modifier contents.\n`$permissions` rules are applied independently from any other rule type.",
6664
6890
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#permissions-modifier",
6665
6891
  version_added:"CoreLibs 1.11",
@@ -6672,7 +6898,7 @@ var data$l = { adg_os_any:{ name:"permissions",
6672
6898
  value_optional:true,
6673
6899
  value_format:"permissions_value" } };
6674
6900
 
6675
- var data$k = { adg_any:{ name:"ping",
6901
+ var data$l = { adg_any:{ name:"ping",
6676
6902
  description:"The rule corresponds to requests caused by either navigator.sendBeacon() or the ping attribute on links.",
6677
6903
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#ping-modifier" },
6678
6904
  abp_any:{ name:"ping",
@@ -6682,13 +6908,13 @@ var data$k = { adg_any:{ name:"ping",
6682
6908
  description:"The rule corresponds to requests caused by either navigator.sendBeacon() or the ping attribute on links.",
6683
6909
  docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" } };
6684
6910
 
6685
- var data$j = { ubo_ext_any:{ name:"popunder",
6911
+ var data$k = { ubo_ext_any:{ name:"popunder",
6686
6912
  description:"To block \"popunders\" windows/tabs where the original page redirects to an advertisement\nand the desired content loads in the newly created one.\nTo be used in the same manner as the popup filter option, except that it will block popunders.",
6687
6913
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#popunder",
6688
6914
  negatable:false,
6689
6915
  block_only:true } };
6690
6916
 
6691
- var data$i = { adg_any:{ name:"popup",
6917
+ var data$j = { adg_any:{ name:"popup",
6692
6918
  description:"Pages opened in a new tab or window.\nNote: Filters will not block pop-ups by default, only if the `$popup` type option is specified.",
6693
6919
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#popup-modifier",
6694
6920
  negatable:false },
@@ -6701,7 +6927,7 @@ var data$i = { adg_any:{ name:"popup",
6701
6927
  docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options",
6702
6928
  negatable:false } };
6703
6929
 
6704
- var data$h = { adg_os_any:{ name:"redirect-rule",
6930
+ var data$i = { adg_os_any:{ name:"redirect-rule",
6705
6931
  description:"This is basically an alias to `$redirect`\nsince it has the same \"redirection\" values and the logic is almost similar.\nThe difference is that `$redirect-rule` is applied only in the case\nwhen the target request is blocked by a different basic rule.",
6706
6932
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#redirect-rule-modifier",
6707
6933
  conflicts:[ "domain",
@@ -6792,7 +7018,7 @@ var data$h = { adg_os_any:{ name:"redirect-rule",
6792
7018
  negatable:false,
6793
7019
  value_format:"(?x)\n ^(\n 1x1\\.gif|\n 2x2\\.png|\n 3x2\\.png|\n 32x32\\.png|\n noop\\.css|\n noop\\.html|\n noopframe|\n noop\\.js|\n noop\\.txt|\n noop-0\\.1s\\.mp3|\n noop-0\\.5s\\.mp3|\n noop-1s\\.mp4|\n none|\n click2load\\.html|\n addthis_widget\\.js|\n amazon_ads\\.js|\n amazon_apstag\\.js|\n monkeybroker\\.js|\n doubleclick_instream_ad_status|\n google-analytics_ga\\.js|\n google-analytics_analytics\\.js|\n google-analytics_inpage_linkid\\.js|\n google-analytics_cx_api\\.js|\n google-ima\\.js|\n googletagservices_gpt\\.js|\n googletagmanager_gtm\\.js|\n googlesyndication_adsbygoogle\\.js|\n scorecardresearch_beacon\\.js|\n outbrain-widget\\.js|\n hd-main\\.js\n )\n (:[0-9]+)?$" } };
6794
7020
 
6795
- var data$g = { adg_os_any:{ name:"redirect",
7021
+ var data$h = { adg_os_any:{ name:"redirect",
6796
7022
  description:"Used to redirect web requests to a local \"resource\".",
6797
7023
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#redirect-modifier",
6798
7024
  assignable:true,
@@ -6820,6 +7046,17 @@ var data$g = { adg_os_any:{ name:"redirect",
6820
7046
  negatable:false,
6821
7047
  value_format:"(?x)\n # ABP resources always starts with the `abp-resource:` prefix\n ^abp-resource:\n # Possible resource names\n (\n blank-text|\n blank-css|\n blank-js|\n blank-html|\n blank-mp3|\n 1x1-transparent-gif|\n 2x2-transparent-png|\n 3x2-transparent-png|\n 32x32-transparent-png\n )$" } };
6822
7048
 
7049
+ var data$g = { adg_os_any:{ name:"referrerpolicy",
7050
+ description:"This modifier allows overriding of a page's referrer policy.",
7051
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#referrerpolicy-modifier",
7052
+ conflicts:[ "document",
7053
+ "subdocument" ],
7054
+ inverse_conflicts:true,
7055
+ assignable:true,
7056
+ negatable:false,
7057
+ value_optional:true,
7058
+ value_format:"referrerpolicy_value" } };
7059
+
6823
7060
  var data$f = { adg_os_any:{ name:"removeheader",
6824
7061
  description:"Rules with the `$removeheader` modifier are intended to remove headers from HTTP requests and responses.",
6825
7062
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#removeheader-modifier",
@@ -7129,46 +7366,47 @@ var data = { adg_any:{ name:"xmlhttprequest",
7129
7366
  // @ts-nocheck
7130
7367
  // Please keep imports and exports in alphabetical order
7131
7368
  const rawModifiersData = {
7132
- all: data$T,
7133
- app: data$S,
7134
- badfilter: data$R,
7135
- cname: data$Q,
7136
- content: data$P,
7137
- cookie: data$O,
7138
- csp: data$N,
7139
- denyallow: data$M,
7140
- document: data$L,
7141
- domain: data$K,
7142
- elemhide: data$J,
7143
- empty: data$I,
7144
- firstParty: data$H,
7145
- extension: data$G,
7146
- font: data$F,
7147
- genericblock: data$E,
7148
- generichide: data$D,
7149
- header: data$C,
7150
- hls: data$B,
7151
- image: data$A,
7152
- important: data$z,
7153
- inlineFont: data$y,
7154
- inlineScript: data$x,
7155
- jsinject: data$w,
7156
- jsonprune: data$v,
7157
- matchCase: data$u,
7158
- media: data$t,
7159
- method: data$s,
7160
- mp4: data$r,
7161
- network: data$q,
7162
- noop: data$p,
7163
- objectSubrequest: data$o,
7164
- object: data$n,
7165
- other: data$m,
7166
- permissions: data$l,
7167
- ping: data$k,
7168
- popunder: data$j,
7169
- popup: data$i,
7170
- redirectRule: data$h,
7171
- redirect: data$g,
7369
+ all: data$U,
7370
+ app: data$T,
7371
+ badfilter: data$S,
7372
+ cname: data$R,
7373
+ content: data$Q,
7374
+ cookie: data$P,
7375
+ csp: data$O,
7376
+ denyallow: data$N,
7377
+ document: data$M,
7378
+ domain: data$L,
7379
+ elemhide: data$K,
7380
+ empty: data$J,
7381
+ firstParty: data$I,
7382
+ extension: data$H,
7383
+ font: data$G,
7384
+ genericblock: data$F,
7385
+ generichide: data$E,
7386
+ header: data$D,
7387
+ hls: data$C,
7388
+ image: data$B,
7389
+ important: data$A,
7390
+ inlineFont: data$z,
7391
+ inlineScript: data$y,
7392
+ jsinject: data$x,
7393
+ jsonprune: data$w,
7394
+ matchCase: data$v,
7395
+ media: data$u,
7396
+ method: data$t,
7397
+ mp4: data$s,
7398
+ network: data$r,
7399
+ noop: data$q,
7400
+ objectSubrequest: data$p,
7401
+ object: data$o,
7402
+ other: data$n,
7403
+ permissions: data$m,
7404
+ ping: data$l,
7405
+ popunder: data$k,
7406
+ popup: data$j,
7407
+ redirectRule: data$i,
7408
+ redirect: data$h,
7409
+ referrerpolicy: data$g,
7172
7410
  removeheader: data$f,
7173
7411
  removeparam: data$e,
7174
7412
  replace: data$d,
@@ -7361,7 +7599,7 @@ const ALLOWED_CSP_DIRECTIVES = new Set([
7361
7599
  'worker-src',
7362
7600
  ]);
7363
7601
  /**
7364
- * Allowed stealth options for $permissions modifier.
7602
+ * Allowed directives for $permissions modifier.
7365
7603
  *
7366
7604
  * @see {@link https://adguard.app/kb/general/ad-filtering/create-own-filters/#permissions-modifier}
7367
7605
  */
@@ -7411,6 +7649,21 @@ const PERMISSIONS_TOKEN_SELF = 'self';
7411
7649
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Permissions_Policy#allowlists}
7412
7650
  */
7413
7651
  const EMPTY_PERMISSIONS_ALLOWLIST = `${OPEN_PARENTHESIS}${CLOSE_PARENTHESIS}`;
7652
+ /**
7653
+ * Allowed directives for $referrerpolicy modifier.
7654
+ *
7655
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy}
7656
+ */
7657
+ const REFERRER_POLICY_DIRECTIVES = new Set([
7658
+ 'no-referrer',
7659
+ 'no-referrer-when-downgrade',
7660
+ 'origin',
7661
+ 'origin-when-cross-origin',
7662
+ 'same-origin',
7663
+ 'strict-origin',
7664
+ 'strict-origin-when-cross-origin',
7665
+ 'unsafe-url',
7666
+ ]);
7414
7667
  /**
7415
7668
  * Prefixes for error messages used in modifier validation.
7416
7669
  */
@@ -7423,6 +7676,7 @@ const VALIDATION_ERROR_PREFIX = {
7423
7676
  INVALID_PERMISSION_DIRECTIVE: 'Invalid Permissions-Policy directive for the modifier',
7424
7677
  INVALID_PERMISSION_ORIGINS: 'Origins in the value is invalid for the modifier and the directive',
7425
7678
  INVALID_PERMISSION_ORIGIN_QUOTES: 'Double quotes should be used for origins in the value of the modifier',
7679
+ INVALID_REFERRER_POLICY_DIRECTIVE: 'Invalid Referrer-Policy directive for the modifier',
7426
7680
  MIXED_NEGATIONS: 'Simultaneous usage of negated and not negated values is forbidden for the modifier',
7427
7681
  NO_CSP_VALUE: 'No CSP value for the modifier and the directive',
7428
7682
  NO_CSP_DIRECTIVE_QUOTE: 'CSP directives should no be quoted for the modifier',
@@ -7541,14 +7795,14 @@ const getSpecificBlockerData = (modifiersData, blockerPrefix, modifierName) => {
7541
7795
  * @example
7542
7796
  * `example.*` — matches with any TLD, e.g. `example.org`, `example.com`, etc.
7543
7797
  */
7544
- const WILDCARD_TLD = DOT + WILDCARD$1;
7798
+ const WILDCARD_TLD = DOT + WILDCARD;
7545
7799
  /**
7546
7800
  * Marker for a wildcard subdomain — `*.`.
7547
7801
  *
7548
7802
  * @example
7549
7803
  * `*.example.org` — matches with any subdomain, e.g. `foo.example.org` or `bar.example.org`
7550
7804
  */
7551
- const WILDCARD_SUBDOMAIN = WILDCARD$1 + DOT;
7805
+ const WILDCARD_SUBDOMAIN = WILDCARD + DOT;
7552
7806
  class DomainUtils {
7553
7807
  /**
7554
7808
  * Check if the input is a valid domain or hostname.
@@ -7559,7 +7813,7 @@ class DomainUtils {
7559
7813
  static isValidDomainOrHostname(domain) {
7560
7814
  let domainToCheck = domain;
7561
7815
  // Wildcard-only domain, typically a generic rule
7562
- if (domainToCheck === WILDCARD$1) {
7816
+ if (domainToCheck === WILDCARD) {
7563
7817
  return true;
7564
7818
  }
7565
7819
  // https://adguard.com/kb/general/ad-filtering/create-own-filters/#wildcard-for-tld
@@ -7746,6 +8000,7 @@ var CustomValueFormatValidatorName;
7746
8000
  CustomValueFormatValidatorName["Domain"] = "pipe_separated_domains";
7747
8001
  CustomValueFormatValidatorName["Method"] = "pipe_separated_methods";
7748
8002
  CustomValueFormatValidatorName["Permissions"] = "permissions_value";
8003
+ CustomValueFormatValidatorName["ReferrerPolicy"] = "referrerpolicy_value";
7749
8004
  CustomValueFormatValidatorName["StealthOption"] = "pipe_separated_stealth_options";
7750
8005
  })(CustomValueFormatValidatorName || (CustomValueFormatValidatorName = {}));
7751
8006
  /**
@@ -7779,7 +8034,7 @@ const isValidAppNameChunk = (chunk) => {
7779
8034
  const isValidAppModifierValue = (value) => {
7780
8035
  // $app modifier does not support wildcard tld
7781
8036
  // https://adguard.app/kb/general/ad-filtering/create-own-filters/#app-modifier
7782
- if (value.includes(WILDCARD$1)) {
8037
+ if (value.includes(WILDCARD)) {
7783
8038
  return false;
7784
8039
  }
7785
8040
  return value
@@ -7844,7 +8099,7 @@ const isValidDenyAllowModifierValue = (value) => {
7844
8099
  // $denyallow modifier does not support wildcard tld
7845
8100
  // https://adguard.app/kb/general/ad-filtering/create-own-filters/#denyallow-modifier
7846
8101
  // but here we are simply checking whether the value contains wildcard `*`, not ends with `.*`
7847
- if (value.includes(WILDCARD$1)) {
8102
+ if (value.includes(WILDCARD)) {
7848
8103
  return false;
7849
8104
  }
7850
8105
  // TODO: add cache for domains validation
@@ -8141,7 +8396,7 @@ const validatePermissionAllowlist = (allowlist, directive, modifierName) => {
8141
8396
  // `*` is one of available permissions tokens
8142
8397
  // e.g. 'fullscreen=*'
8143
8398
  // https://w3c.github.io/webappsec-permissions-policy/#structured-header-serialization
8144
- if (allowlist === WILDCARD$1
8399
+ if (allowlist === WILDCARD
8145
8400
  // e.g. 'autoplay=()'
8146
8401
  || allowlist === EMPTY_PERMISSIONS_ALLOWLIST) {
8147
8402
  return { valid: true };
@@ -8202,6 +8457,26 @@ const validatePermissions = (modifier) => {
8202
8457
  }
8203
8458
  return { valid: true };
8204
8459
  };
8460
+ /**
8461
+ * Validates `referrerpolicy_value` custom value format.
8462
+ * Used for $referrerpolicy modifier.
8463
+ *
8464
+ * @param modifier Modifier AST node.
8465
+ *
8466
+ * @returns Validation result.
8467
+ */
8468
+ const validateReferrerPolicy = (modifier) => {
8469
+ if (!modifier.value?.value) {
8470
+ return getValueRequiredValidationResult(modifier.modifier.value);
8471
+ }
8472
+ const modifierName = modifier.modifier.value;
8473
+ const modifierValue = modifier.value.value;
8474
+ if (!REFERRER_POLICY_DIRECTIVES.has(modifierValue)) {
8475
+ // eslint-disable-next-line max-len
8476
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.INVALID_REFERRER_POLICY_DIRECTIVE}: '${modifierName}': '${modifierValue}'`);
8477
+ }
8478
+ return { valid: true };
8479
+ };
8205
8480
  /**
8206
8481
  * Map of all available pre-defined validators for modifiers with custom `value_format`.
8207
8482
  */
@@ -8212,6 +8487,7 @@ const CUSTOM_VALUE_FORMAT_MAP = {
8212
8487
  [CustomValueFormatValidatorName.Domain]: validatePipeSeparatedDomains,
8213
8488
  [CustomValueFormatValidatorName.Method]: validatePipeSeparatedMethods,
8214
8489
  [CustomValueFormatValidatorName.Permissions]: validatePermissions,
8490
+ [CustomValueFormatValidatorName.ReferrerPolicy]: validateReferrerPolicy,
8215
8491
  [CustomValueFormatValidatorName.StealthOption]: validatePipeSeparatedStealthOptions,
8216
8492
  };
8217
8493
  /**
@@ -8405,7 +8681,7 @@ class ModifierValidator {
8405
8681
  * @returns Result of modifier validation.
8406
8682
  */
8407
8683
  validate = (syntax, rawModifier, isException = false) => {
8408
- const modifier = cloneDeep(rawModifier);
8684
+ const modifier = clone(rawModifier);
8409
8685
  // special case: handle noop modifier which may be used as multiple underscores (not just one)
8410
8686
  // https://adguard.com/kb/general/ad-filtering/create-own-filters/#noop-modifier
8411
8687
  if (modifier.modifier.value.startsWith(UNDERSCORE)) {
@@ -8484,7 +8760,9 @@ class ConverterBase {
8484
8760
  * Converts some data to AdGuard format
8485
8761
  *
8486
8762
  * @param data Data to convert
8487
- * @returns Converted data
8763
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
8764
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
8765
+ * If the node was not converted, the result will contain the original node with the same object reference
8488
8766
  * @throws If the data is invalid or incompatible
8489
8767
  */
8490
8768
  static convertToAdg(data) {
@@ -8494,7 +8772,9 @@ class ConverterBase {
8494
8772
  * Converts some data to Adblock Plus format
8495
8773
  *
8496
8774
  * @param data Data to convert
8497
- * @returns Converted data
8775
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
8776
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
8777
+ * If the node was not converted, the result will contain the original node with the same object reference
8498
8778
  * @throws If the data is invalid or incompatible
8499
8779
  */
8500
8780
  static convertToAbp(data) {
@@ -8504,7 +8784,9 @@ class ConverterBase {
8504
8784
  * Converts some data to uBlock Origin format
8505
8785
  *
8506
8786
  * @param data Data to convert
8507
- * @returns Converted data
8787
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
8788
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
8789
+ * If the node was not converted, the result will contain the original node with the same object reference
8508
8790
  * @throws If the data is invalid or incompatible
8509
8791
  */
8510
8792
  static convertToUbo(data) {
@@ -8528,7 +8810,9 @@ class RuleConverterBase extends ConverterBase {
8528
8810
  * Converts an adblock filtering rule to AdGuard format, if possible.
8529
8811
  *
8530
8812
  * @param rule Rule node to convert
8531
- * @returns Array of converted rule nodes
8813
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
8814
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
8815
+ * If the rule was not converted, the result array will contain the original node with the same object reference
8532
8816
  * @throws If the rule is invalid or cannot be converted
8533
8817
  */
8534
8818
  static convertToAdg(rule) {
@@ -8538,7 +8822,9 @@ class RuleConverterBase extends ConverterBase {
8538
8822
  * Converts an adblock filtering rule to Adblock Plus format, if possible.
8539
8823
  *
8540
8824
  * @param rule Rule node to convert
8541
- * @returns Array of converted rule nodes
8825
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
8826
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
8827
+ * If the rule was not converted, the result array will contain the original node with the same object reference
8542
8828
  * @throws If the rule is invalid or cannot be converted
8543
8829
  */
8544
8830
  static convertToAbp(rule) {
@@ -8548,7 +8834,9 @@ class RuleConverterBase extends ConverterBase {
8548
8834
  * Converts an adblock filtering rule to uBlock Origin format, if possible.
8549
8835
  *
8550
8836
  * @param rule Rule node to convert
8551
- * @returns Array of converted rule nodes
8837
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
8838
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
8839
+ * If the rule was not converted, the result array will contain the original node with the same object reference
8552
8840
  * @throws If the rule is invalid or cannot be converted
8553
8841
  */
8554
8842
  static convertToUbo(rule) {
@@ -8556,6 +8844,37 @@ class RuleConverterBase extends ConverterBase {
8556
8844
  }
8557
8845
  }
8558
8846
 
8847
+ /**
8848
+ * @file Conversion result interface and helper functions
8849
+ */
8850
+ /**
8851
+ * Helper function to create a generic conversion result.
8852
+ *
8853
+ * @param result Conversion result
8854
+ * @param isConverted Indicates whether the input item was converted
8855
+ * @template T Type of the item to convert
8856
+ * @template U Type of the conversion result (defaults to `T`, but can be `T[]` as well)
8857
+ * @returns Generic conversion result
8858
+ */
8859
+ // eslint-disable-next-line max-len
8860
+ function createConversionResult(result, isConverted) {
8861
+ return {
8862
+ result,
8863
+ isConverted,
8864
+ };
8865
+ }
8866
+ /**
8867
+ * Helper function to create a node conversion result.
8868
+ *
8869
+ * @param nodes Array of nodes
8870
+ * @param isConverted Indicates whether the input item was converted
8871
+ * @template T Type of the node (extends `Node`)
8872
+ * @returns Node conversion result
8873
+ */
8874
+ function createNodeConversionResult(nodes, isConverted) {
8875
+ return createConversionResult(nodes, isConverted);
8876
+ }
8877
+
8559
8878
  /**
8560
8879
  * @file Comment rule converter
8561
8880
  */
@@ -8569,27 +8888,30 @@ class CommentRuleConverter extends RuleConverterBase {
8569
8888
  * Converts a comment rule to AdGuard format, if possible.
8570
8889
  *
8571
8890
  * @param rule Rule node to convert
8572
- * @returns Array of converted rule nodes
8891
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
8892
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
8893
+ * If the rule was not converted, the result array will contain the original node with the same object reference
8573
8894
  * @throws If the rule is invalid or cannot be converted
8574
8895
  */
8575
8896
  static convertToAdg(rule) {
8576
- // Clone the provided AST node to avoid side effects
8577
- const ruleNode = cloneDeep(rule);
8578
8897
  // TODO: Add support for other comment types, if needed
8579
8898
  // Main task is # -> ! conversion
8580
- switch (ruleNode.type) {
8899
+ switch (rule.type) {
8581
8900
  case CommentRuleType.CommentRule:
8582
- // 'Comment' uBO style comments
8583
- if (ruleNode.type === CommentRuleType.CommentRule
8584
- && ruleNode.marker.value === CommentMarker.Hashmark) {
8585
- ruleNode.marker.value = CommentMarker.Regular;
8586
- // Add the hashmark to the beginning of the comment
8587
- ruleNode.text.value = `${SPACE}${CommentMarker.Hashmark}${ruleNode.text.value}`;
8901
+ // Check if the rule needs to be converted
8902
+ if (rule.type === CommentRuleType.CommentRule && rule.marker.value === CommentMarker.Hashmark) {
8903
+ // Add a ! to the beginning of the comment
8904
+ // TODO: Replace with custom clone method
8905
+ const ruleClone = clone(rule);
8906
+ ruleClone.marker.value = CommentMarker.Regular;
8907
+ // Add the hashmark to the beginning of the comment text
8908
+ ruleClone.text.value = `${SPACE}${CommentMarker.Hashmark}${ruleClone.text.value}`;
8909
+ return createNodeConversionResult([ruleClone], true);
8588
8910
  }
8589
- return [ruleNode];
8911
+ return createNodeConversionResult([rule], false);
8590
8912
  // Leave any other comment rule as is
8591
8913
  default:
8592
- return [ruleNode];
8914
+ return createNodeConversionResult([rule], false);
8593
8915
  }
8594
8916
  }
8595
8917
  }
@@ -8759,6 +9081,58 @@ class RegExpUtils {
8759
9081
  }
8760
9082
  }
8761
9083
 
9084
+ /**
9085
+ * @file Custom clone functions for AST nodes, this is probably the most efficient way to clone AST nodes.
9086
+ * @todo Maybe move them to parser classes as 'clone' methods
9087
+ */
9088
+ /**
9089
+ * Clones a scriptlet rule node.
9090
+ *
9091
+ * @param node Node to clone
9092
+ * @returns Cloned node
9093
+ */
9094
+ function cloneScriptletRuleNode(node) {
9095
+ return {
9096
+ type: node.type,
9097
+ children: node.children.map((child) => ({ ...child })),
9098
+ };
9099
+ }
9100
+ /**
9101
+ * Clones a domain list node.
9102
+ *
9103
+ * @param node Node to clone
9104
+ * @returns Cloned node
9105
+ */
9106
+ function cloneDomainListNode(node) {
9107
+ return {
9108
+ type: node.type,
9109
+ separator: node.separator,
9110
+ children: node.children.map((domain) => ({ ...domain })),
9111
+ };
9112
+ }
9113
+ /**
9114
+ * Clones a modifier list node.
9115
+ *
9116
+ * @param node Node to clone
9117
+ * @returns Cloned node
9118
+ */
9119
+ function cloneModifierListNode(node) {
9120
+ return {
9121
+ type: node.type,
9122
+ children: node.children.map((modifier) => {
9123
+ const res = {
9124
+ type: modifier.type,
9125
+ exception: modifier.exception,
9126
+ modifier: { ...modifier.modifier },
9127
+ };
9128
+ if (modifier.value) {
9129
+ res.value = { ...modifier.value };
9130
+ }
9131
+ return res;
9132
+ }),
9133
+ };
9134
+ }
9135
+
8762
9136
  /**
8763
9137
  * @file HTML filtering rule converter
8764
9138
  */
@@ -8771,16 +9145,22 @@ class RegExpUtils {
8771
9145
  *
8772
9146
  * @see {@link https://adguard.com/kb/general/ad-filtering/create-own-filters/#html-filtering-rules}
8773
9147
  */
8774
- const ADGUARD_HTML_DEFAULT_MAX_LENGTH = 8192;
8775
- const ADGUARD_HTML_CONVERSION_MAX_LENGTH = ADGUARD_HTML_DEFAULT_MAX_LENGTH * 32;
9148
+ const ADG_HTML_DEFAULT_MAX_LENGTH = 8192;
9149
+ const ADG_HTML_CONVERSION_MAX_LENGTH = ADG_HTML_DEFAULT_MAX_LENGTH * 32;
8776
9150
  const NOT_SPECIFIED = -1;
8777
- const CONTAINS$1 = 'contains';
8778
- const HAS_TEXT$1 = 'has-text';
8779
- const MAX_LENGTH = 'max-length';
8780
- const MIN_LENGTH = 'min-length';
8781
- const MIN_TEXT_LENGTH = 'min-text-length';
8782
- const TAG_CONTENT = 'tag-content';
8783
- const WILDCARD = 'wildcard';
9151
+ var PseudoClasses$1;
9152
+ (function (PseudoClasses) {
9153
+ PseudoClasses["Contains"] = "contains";
9154
+ PseudoClasses["HasText"] = "has-text";
9155
+ PseudoClasses["MinTextLength"] = "min-text-length";
9156
+ })(PseudoClasses$1 || (PseudoClasses$1 = {}));
9157
+ var AttributeSelectors;
9158
+ (function (AttributeSelectors) {
9159
+ AttributeSelectors["MaxLength"] = "max-length";
9160
+ AttributeSelectors["MinLength"] = "min-length";
9161
+ AttributeSelectors["TagContent"] = "tag-content";
9162
+ AttributeSelectors["Wildcard"] = "wildcard";
9163
+ })(AttributeSelectors || (AttributeSelectors = {}));
8784
9164
  /**
8785
9165
  * HTML filtering rule converter class
8786
9166
  *
@@ -8803,16 +9183,23 @@ class HtmlRuleConverter extends RuleConverterBase {
8803
9183
  * ```
8804
9184
  *
8805
9185
  * @param rule Rule node to convert
8806
- * @returns Array of converted rule nodes
9186
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
9187
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
9188
+ * If the rule was not converted, the result array will contain the original node with the same object reference
8807
9189
  * @throws If the rule is invalid or cannot be converted
8808
9190
  */
8809
9191
  static convertToAdg(rule) {
8810
- // Clone the provided AST node to avoid side effects
8811
- const ruleNode = cloneDeep(rule);
9192
+ // Ignore AdGuard rules
9193
+ if (rule.syntax === AdblockSyntax.Adg) {
9194
+ return createNodeConversionResult([rule], false);
9195
+ }
9196
+ if (rule.syntax === AdblockSyntax.Abp) {
9197
+ throw new RuleConversionError('Invalid rule, ABP does not support HTML filtering rules');
9198
+ }
8812
9199
  // Prepare the conversion result
8813
9200
  const conversionResult = [];
8814
9201
  // Iterate over selector list
8815
- for (const selector of ruleNode.body.body.children) {
9202
+ for (const selector of rule.body.body.children) {
8816
9203
  // Check selector, just in case
8817
9204
  if (selector.type !== CssTreeNodeType.Selector) {
8818
9205
  throw new RuleConversionError(`Expected selector, got '${selector.type}'`);
@@ -8839,24 +9226,24 @@ class HtmlRuleConverter extends RuleConverterBase {
8839
9226
  throw new RuleConversionError('Tag selector should be the first child, if present');
8840
9227
  }
8841
9228
  // Simply store the tag selector
8842
- convertedSelector.children.push(cloneDeep(node));
9229
+ convertedSelector.children.push(clone(node));
8843
9230
  break;
8844
9231
  case CssTreeNodeType.AttributeSelector:
8845
9232
  // Check if the attribute selector is a special AdGuard attribute
8846
9233
  switch (node.name.name) {
8847
- case MIN_LENGTH:
9234
+ case AttributeSelectors.MinLength:
8848
9235
  minLength = CssTree.parseAttributeSelectorValueAsNumber(node);
8849
9236
  break;
8850
- case MAX_LENGTH:
9237
+ case AttributeSelectors.MaxLength:
8851
9238
  maxLength = CssTree.parseAttributeSelectorValueAsNumber(node);
8852
9239
  break;
8853
- case TAG_CONTENT:
8854
- case WILDCARD:
9240
+ case AttributeSelectors.TagContent:
9241
+ case AttributeSelectors.Wildcard:
8855
9242
  CssTree.assertAttributeSelectorHasStringValue(node);
8856
- convertedSelector.children.push(cloneDeep(node));
9243
+ convertedSelector.children.push(clone(node));
8857
9244
  break;
8858
9245
  default:
8859
- convertedSelector.children.push(cloneDeep(node));
9246
+ convertedSelector.children.push(clone(node));
8860
9247
  }
8861
9248
  break;
8862
9249
  case CssTreeNodeType.PseudoClassSelector:
@@ -8870,18 +9257,18 @@ class HtmlRuleConverter extends RuleConverterBase {
8870
9257
  }
8871
9258
  // Process the pseudo class based on its name
8872
9259
  switch (node.name) {
8873
- case HAS_TEXT$1:
8874
- case CONTAINS$1:
9260
+ case PseudoClasses$1.HasText:
9261
+ case PseudoClasses$1.Contains:
8875
9262
  // Check if the argument is a RegExp
8876
9263
  if (RegExpUtils.isRegexPattern(arg.value)) {
8877
9264
  // TODO: Add some support for RegExp patterns later
8878
9265
  // Need to find a way to convert some RegExp patterns to glob patterns
8879
9266
  throw new RuleConversionError('Conversion of RegExp patterns is not yet supported');
8880
9267
  }
8881
- convertedSelector.children.push(CssTree.createAttributeSelectorNode(TAG_CONTENT, arg.value));
9268
+ convertedSelector.children.push(CssTree.createAttributeSelectorNode(AttributeSelectors.TagContent, arg.value));
8882
9269
  break;
8883
9270
  // https://github.com/gorhill/uBlock/wiki/Procedural-cosmetic-filters#subjectmin-text-lengthn
8884
- case MIN_TEXT_LENGTH:
9271
+ case PseudoClasses$1.MinTextLength:
8885
9272
  minLength = CssTree.parsePseudoClassArgumentAsNumber(node);
8886
9273
  break;
8887
9274
  default:
@@ -8893,10 +9280,10 @@ class HtmlRuleConverter extends RuleConverterBase {
8893
9280
  }
8894
9281
  }
8895
9282
  if (minLength !== NOT_SPECIFIED) {
8896
- convertedSelector.children.push(CssTree.createAttributeSelectorNode(MIN_LENGTH, String(minLength)));
9283
+ convertedSelector.children.push(CssTree.createAttributeSelectorNode(AttributeSelectors.MinLength, String(minLength)));
8897
9284
  }
8898
- convertedSelector.children.push(CssTree.createAttributeSelectorNode(MAX_LENGTH, String(maxLength === NOT_SPECIFIED
8899
- ? ADGUARD_HTML_CONVERSION_MAX_LENGTH
9285
+ convertedSelector.children.push(CssTree.createAttributeSelectorNode(AttributeSelectors.MaxLength, String(maxLength === NOT_SPECIFIED
9286
+ ? ADG_HTML_CONVERSION_MAX_LENGTH
8900
9287
  : maxLength)));
8901
9288
  // Create the converted rule
8902
9289
  conversionResult.push({
@@ -8906,7 +9293,7 @@ class HtmlRuleConverter extends RuleConverterBase {
8906
9293
  // Convert the separator based on the exception status
8907
9294
  separator: {
8908
9295
  type: 'Value',
8909
- value: ruleNode.exception
9296
+ value: rule.exception
8910
9297
  ? CosmeticRuleSeparator.AdgHtmlFilteringException
8911
9298
  : CosmeticRuleSeparator.AdgHtmlFiltering,
8912
9299
  },
@@ -8921,11 +9308,11 @@ class HtmlRuleConverter extends RuleConverterBase {
8921
9308
  }],
8922
9309
  },
8923
9310
  },
8924
- exception: ruleNode.exception,
8925
- domains: ruleNode.domains,
9311
+ exception: rule.exception,
9312
+ domains: cloneDomainListNode(rule.domains),
8926
9313
  });
8927
9314
  }
8928
- return conversionResult;
9315
+ return createNodeConversionResult(conversionResult, true);
8929
9316
  }
8930
9317
  }
8931
9318
 
@@ -8946,96 +9333,38 @@ function getScriptletName(scriptletNode) {
8946
9333
  return scriptletNode.children[0].value;
8947
9334
  }
8948
9335
  /**
8949
- * Set name of the scriptlet
9336
+ * Set name of the scriptlet.
9337
+ * Modifies input `scriptletNode` if needed.
8950
9338
  *
8951
9339
  * @param scriptletNode Scriptlet node to set name of
8952
9340
  * @param name Name to set
8953
- * @returns Scriptlet node with the specified name
8954
- * @throws If the scriptlet is empty
8955
9341
  */
8956
9342
  function setScriptletName(scriptletNode, name) {
8957
- if (scriptletNode.children.length === 0) {
8958
- throw new Error('Empty scriptlet');
9343
+ if (scriptletNode.children.length > 0) {
9344
+ // eslint-disable-next-line no-param-reassign
9345
+ scriptletNode.children[0].value = name;
8959
9346
  }
8960
- const scriptletNodeClone = cloneDeep(scriptletNode);
8961
- scriptletNodeClone.children[0].value = name;
8962
- return scriptletNodeClone;
8963
9347
  }
8964
9348
  /**
8965
9349
  * Set quote type of the scriptlet parameters
8966
9350
  *
8967
9351
  * @param scriptletNode Scriptlet node to set quote type of
8968
9352
  * @param quoteType Preferred quote type
8969
- * @returns Scriptlet node with the specified quote type
8970
9353
  */
8971
9354
  function setScriptletQuoteType(scriptletNode, quoteType) {
8972
- if (scriptletNode.children.length === 0) {
8973
- throw new Error('Empty scriptlet');
8974
- }
8975
- const scriptletNodeClone = cloneDeep(scriptletNode);
8976
- for (let i = 0; i < scriptletNodeClone.children.length; i += 1) {
8977
- scriptletNodeClone.children[i].value = QuoteUtils.setStringQuoteType(scriptletNodeClone.children[i].value, quoteType);
8978
- }
8979
- return scriptletNodeClone;
8980
- }
8981
-
8982
- /**
8983
- * @file Scriptlet conversions from ABP and uBO to ADG
8984
- */
8985
- const ABP_SCRIPTLET_PREFIX = 'abp-';
8986
- const UBO_SCRIPTLET_PREFIX = 'ubo-';
8987
- /**
8988
- * Helper class for converting scriptlets from ABP and uBO to ADG
8989
- */
8990
- class AdgScriptletConverter {
8991
- /**
8992
- * Helper function to convert scriptlets to ADG. We implement the core
8993
- * logic here to avoid code duplication.
8994
- *
8995
- * @param scriptletNode Scriptlet parameter list node to convert
8996
- * @param prefix Prefix to add to the scriptlet name
8997
- * @returns Converted scriptlet parameter list node
8998
- */
8999
- static convertToAdg(scriptletNode, prefix) {
9000
- // Remove possible quotes just to make it easier to work with the scriptlet name
9001
- const scriptletName = QuoteUtils.setStringQuoteType(getScriptletName(scriptletNode), QuoteType.None);
9002
- // Clone the node to avoid any side effects
9003
- let result = cloneDeep(scriptletNode);
9004
- // Only add prefix if it's not already there
9005
- if (!scriptletName.startsWith(prefix)) {
9006
- result = setScriptletName(scriptletNode, `${prefix}${scriptletName}`);
9355
+ if (scriptletNode.children.length > 0) {
9356
+ for (let i = 0; i < scriptletNode.children.length; i += 1) {
9357
+ // eslint-disable-next-line no-param-reassign
9358
+ scriptletNode.children[i].value = QuoteUtils.setStringQuoteType(scriptletNode.children[i].value, quoteType);
9007
9359
  }
9008
- // ADG scriptlet parameters should be quoted, and single quoted are preferred
9009
- result = setScriptletQuoteType(result, QuoteType.Single);
9010
- return result;
9011
9360
  }
9012
- /**
9013
- * Converts an ABP snippet node to ADG scriptlet node, if possible.
9014
- *
9015
- * @param scriptletNode Scriptlet node to convert
9016
- * @returns Converted scriptlet node
9017
- * @throws If the scriptlet isn't supported by ADG or is invalid
9018
- * @see {@link https://help.adblockplus.org/hc/en-us/articles/1500002338501#snippets-ref}
9019
- */
9020
- static convertFromAbp = (scriptletNode) => {
9021
- return AdgScriptletConverter.convertToAdg(scriptletNode, ABP_SCRIPTLET_PREFIX);
9022
- };
9023
- /**
9024
- * Convert a uBO scriptlet node to ADG scriptlet node, if possible.
9025
- *
9026
- * @param scriptletNode Scriptlet node to convert
9027
- * @returns Converted scriptlet node
9028
- * @throws If the scriptlet isn't supported by ADG or is invalid
9029
- * @see {@link https://github.com/gorhill/uBlock/wiki/Resources-Library#available-general-purpose-scriptlets}
9030
- */
9031
- static convertFromUbo = (scriptletNode) => {
9032
- return AdgScriptletConverter.convertToAdg(scriptletNode, UBO_SCRIPTLET_PREFIX);
9033
- };
9034
9361
  }
9035
9362
 
9036
9363
  /**
9037
9364
  * @file Scriptlet injection rule converter
9038
9365
  */
9366
+ const ABP_SCRIPTLET_PREFIX = 'abp-';
9367
+ const UBO_SCRIPTLET_PREFIX = 'ubo-';
9039
9368
  /**
9040
9369
  * Scriptlet injection rule converter class
9041
9370
  *
@@ -9046,38 +9375,91 @@ class ScriptletRuleConverter extends RuleConverterBase {
9046
9375
  * Converts a scriptlet injection rule to AdGuard format, if possible.
9047
9376
  *
9048
9377
  * @param rule Rule node to convert
9049
- * @returns Array of converted rule nodes
9378
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
9379
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
9380
+ * If the rule was not converted, the result array will contain the original node with the same object reference
9050
9381
  * @throws If the rule is invalid or cannot be converted
9051
9382
  */
9052
9383
  static convertToAdg(rule) {
9053
- // Clone the provided AST node to avoid side effects
9054
- const ruleNode = cloneDeep(rule);
9384
+ // Ignore AdGuard rules
9385
+ if (rule.syntax === AdblockSyntax.Adg) {
9386
+ return createNodeConversionResult([rule], false);
9387
+ }
9388
+ const separator = rule.separator.value;
9389
+ let convertedSeparator = separator;
9390
+ convertedSeparator = rule.exception
9391
+ ? CosmeticRuleSeparator.AdgJsInjectionException
9392
+ : CosmeticRuleSeparator.AdgJsInjection;
9055
9393
  const convertedScriptlets = [];
9056
- for (const scriptlet of ruleNode.body.children) {
9057
- if (ruleNode.syntax === AdblockSyntax.Abp) {
9058
- convertedScriptlets.push(AdgScriptletConverter.convertFromAbp(scriptlet));
9059
- }
9060
- else if (ruleNode.syntax === AdblockSyntax.Ubo) {
9061
- convertedScriptlets.push(AdgScriptletConverter.convertFromUbo(scriptlet));
9394
+ for (const scriptlet of rule.body.children) {
9395
+ // Clone the node to avoid any side effects
9396
+ const scriptletClone = cloneScriptletRuleNode(scriptlet);
9397
+ // Remove possible quotes just to make it easier to work with the scriptlet name
9398
+ const scriptletName = QuoteUtils.setStringQuoteType(getScriptletName(scriptletClone), QuoteType.None);
9399
+ // Add prefix if it's not already there
9400
+ let prefix;
9401
+ switch (rule.syntax) {
9402
+ case AdblockSyntax.Abp:
9403
+ prefix = ABP_SCRIPTLET_PREFIX;
9404
+ break;
9405
+ case AdblockSyntax.Ubo:
9406
+ prefix = UBO_SCRIPTLET_PREFIX;
9407
+ break;
9408
+ default:
9409
+ prefix = EMPTY;
9062
9410
  }
9063
- else if (ruleNode.syntax === AdblockSyntax.Adg) {
9064
- convertedScriptlets.push(scriptlet);
9411
+ if (!scriptletName.startsWith(prefix)) {
9412
+ setScriptletName(scriptletClone, `${prefix}${scriptletName}`);
9065
9413
  }
9414
+ // ADG scriptlet parameters should be quoted, and single quoted are preferred
9415
+ setScriptletQuoteType(scriptletClone, QuoteType.Single);
9416
+ convertedScriptlets.push(scriptletClone);
9066
9417
  }
9067
- ruleNode.separator.value = ruleNode.exception
9068
- ? CosmeticRuleSeparator.AdgJsInjectionException
9069
- : CosmeticRuleSeparator.AdgJsInjection;
9070
- // ADG doesn't support multiple scriptlets in one rule, so we should split them
9071
- return convertedScriptlets.map((scriptlet) => {
9072
- return {
9073
- ...ruleNode,
9418
+ return createNodeConversionResult(convertedScriptlets.map((scriptlet) => {
9419
+ const res = {
9420
+ category: rule.category,
9421
+ type: rule.type,
9074
9422
  syntax: AdblockSyntax.Adg,
9423
+ exception: rule.exception,
9424
+ domains: cloneDomainListNode(rule.domains),
9425
+ separator: {
9426
+ type: 'Value',
9427
+ value: convertedSeparator,
9428
+ },
9075
9429
  body: {
9076
- ...ruleNode.body,
9430
+ type: rule.body.type,
9077
9431
  children: [scriptlet],
9078
9432
  },
9079
9433
  };
9080
- });
9434
+ if (rule.modifiers) {
9435
+ res.modifiers = cloneModifierListNode(rule.modifiers);
9436
+ }
9437
+ return res;
9438
+ }), true);
9439
+ }
9440
+ }
9441
+
9442
+ /**
9443
+ * A very simple map extension that allows to store multiple values for the same key
9444
+ * by storing them in an array.
9445
+ *
9446
+ * @todo Add more methods if needed
9447
+ */
9448
+ class MultiValueMap extends Map {
9449
+ /**
9450
+ * Adds a value to the map. If the key already exists, the value will be appended to the existing array,
9451
+ * otherwise a new array will be created for the key.
9452
+ *
9453
+ * @param key Key to add
9454
+ * @param values Value(s) to add
9455
+ */
9456
+ add(key, ...values) {
9457
+ let currentValues = super.get(key);
9458
+ if (isUndefined(currentValues)) {
9459
+ currentValues = [];
9460
+ super.set(key, values);
9461
+ }
9462
+ currentValues.push(...values);
9081
9463
  }
9082
9464
  }
9083
9465
 
@@ -9103,69 +9485,115 @@ class AdgCosmeticRuleModifierConverter {
9103
9485
  * Converts a uBO cosmetic rule modifier list to ADG, if possible.
9104
9486
  *
9105
9487
  * @param modifierList Cosmetic rule modifier list node to convert
9106
- * @returns Converted cosmetic rule modifier list node
9488
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
9489
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
9490
+ * If the node was not converted, the result will contain the original node with the same object reference
9107
9491
  * @throws If the modifier list cannot be converted
9108
9492
  * @see {@link https://github.com/gorhill/uBlock/wiki/Procedural-cosmetic-filters#cosmetic-filter-operators}
9109
9493
  */
9110
- static convertFromUbo = (modifierList) => {
9111
- const convertedModifierList = createModifierListNode();
9112
- for (const modifier of modifierList.children) {
9113
- let modifierValue;
9114
- switch (modifier.modifier.value) {
9115
- case UBO_MATCHES_PATH_OPERATOR:
9116
- // :matches-path() should have a value
9117
- if (!modifier.value) {
9118
- throw new RuleConversionError('Missing value for :matches-path(...)');
9119
- }
9120
- modifierValue = RegExpUtils.isRegexPattern(modifier.value.value)
9121
- ? StringUtils.escapeCharacters(modifier.value.value, SPECIAL_MODIFIER_REGEX_CHARS)
9122
- : modifier.value.value;
9123
- // Convert uBO's `:matches-path(...)` operator to ADG's `$path=...` modifier
9124
- convertedModifierList.children.push(createModifierNode(ADG_PATH_MODIFIER,
9125
- // We should negate the regexp if the modifier is an exception
9126
- modifier.exception
9127
- // eslint-disable-next-line max-len
9128
- ? `${REGEX_MARKER}${RegExpUtils.negateRegexPattern(RegExpUtils.patternToRegexp(modifierValue))}${REGEX_MARKER}`
9129
- : modifierValue));
9130
- break;
9131
- default:
9132
- // Leave the modifier as-is
9133
- convertedModifierList.children.push(modifier);
9494
+ static convertFromUbo(modifierList) {
9495
+ const conversionMap = new MultiValueMap();
9496
+ modifierList.children.forEach((modifier, index) => {
9497
+ // :matches-path
9498
+ if (modifier.modifier.value === UBO_MATCHES_PATH_OPERATOR) {
9499
+ if (!modifier.value) {
9500
+ throw new RuleConversionError(`'${UBO_MATCHES_PATH_OPERATOR}' operator requires a value`);
9501
+ }
9502
+ const value = RegExpUtils.isRegexPattern(modifier.value.value)
9503
+ ? StringUtils.escapeCharacters(modifier.value.value, SPECIAL_MODIFIER_REGEX_CHARS)
9504
+ : modifier.value.value;
9505
+ // Convert uBO's `:matches-path(...)` operator to ADG's `$path=...` modifier
9506
+ conversionMap.add(index, createModifierNode(ADG_PATH_MODIFIER,
9507
+ // We should negate the regexp if the modifier is an exception
9508
+ modifier.exception
9509
+ // eslint-disable-next-line max-len
9510
+ ? `${REGEX_MARKER}${RegExpUtils.negateRegexPattern(RegExpUtils.patternToRegexp(value))}${REGEX_MARKER}`
9511
+ : value));
9134
9512
  }
9135
- }
9136
- return convertedModifierList;
9137
- };
9513
+ });
9514
+ // Check if we have any converted modifiers
9515
+ if (conversionMap.size) {
9516
+ const modifierListClone = clone(modifierList);
9517
+ // Replace the original modifiers with the converted ones
9518
+ modifierListClone.children = modifierListClone.children.map((modifier, index) => {
9519
+ const convertedModifier = conversionMap.get(index);
9520
+ return convertedModifier ?? modifier;
9521
+ }).flat();
9522
+ return createConversionResult(modifierListClone, true);
9523
+ }
9524
+ // Otherwise, just return the original modifier list
9525
+ return createConversionResult(modifierList, false);
9526
+ }
9138
9527
  }
9139
9528
 
9140
- // Constants for pseudo-classes (please keep them sorted alphabetically)
9141
- const ABP_CONTAINS = '-abp-contains';
9142
- const ABP_HAS = '-abp-has';
9143
- const CONTAINS = 'contains';
9144
- const HAS = 'has';
9145
- const HAS_TEXT = 'has-text';
9146
- const MATCHES_CSS = 'matches-css';
9147
- const MATCHES_CSS_AFTER = 'matches-css-after';
9148
- const MATCHES_CSS_BEFORE = 'matches-css-before';
9149
- const NOT = 'not';
9150
- // Constants for pseudo-elements (please keep them sorted alphabetically)
9151
- const AFTER = 'after';
9152
- const BEFORE = 'before';
9529
+ var PseudoClasses;
9530
+ (function (PseudoClasses) {
9531
+ PseudoClasses["AbpContains"] = "-abp-contains";
9532
+ PseudoClasses["AbpHas"] = "-abp-has";
9533
+ PseudoClasses["Contains"] = "contains";
9534
+ PseudoClasses["Has"] = "has";
9535
+ PseudoClasses["HasText"] = "has-text";
9536
+ PseudoClasses["MatchesCss"] = "matches-css";
9537
+ PseudoClasses["MatchesCssAfter"] = "matches-css-after";
9538
+ PseudoClasses["MatchesCssBefore"] = "matches-css-before";
9539
+ PseudoClasses["Not"] = "not";
9540
+ })(PseudoClasses || (PseudoClasses = {}));
9541
+ var PseudoElements;
9542
+ (function (PseudoElements) {
9543
+ PseudoElements["After"] = "after";
9544
+ PseudoElements["Before"] = "before";
9545
+ })(PseudoElements || (PseudoElements = {}));
9546
+ const PSEUDO_ELEMENT_NAMES = new Set([
9547
+ PseudoElements.After,
9548
+ PseudoElements.Before,
9549
+ ]);
9550
+ const LEGACY_MATCHES_CSS_NAMES = new Set([
9551
+ PseudoClasses.MatchesCssAfter,
9552
+ PseudoClasses.MatchesCssBefore,
9553
+ ]);
9554
+ const LEGACY_EXT_CSS_INDICATOR_PSEUDO_NAMES = new Set([
9555
+ PseudoClasses.Not,
9556
+ PseudoClasses.MatchesCssBefore,
9557
+ PseudoClasses.MatchesCssAfter,
9558
+ ]);
9559
+ const CSS_CONVERSION_INDICATOR_PSEUDO_NAMES = new Set([
9560
+ PseudoClasses.AbpContains,
9561
+ PseudoClasses.AbpHas,
9562
+ PseudoClasses.HasText,
9563
+ ]);
9153
9564
  /**
9154
9565
  * Converts some pseudo-classes to pseudo-elements. For example:
9155
9566
  * - `:before` → `::before`
9156
9567
  *
9157
9568
  * @param selectorList Selector list to convert
9158
- * @returns Converted selector list
9569
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
9570
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
9571
+ * If the node was not converted, the result will contain the original node with the same object reference
9159
9572
  */
9160
9573
  function convertToPseudoElements(selectorList) {
9161
- // Prepare conversion result
9162
- const selectorListClone = cloneDeep(selectorList);
9574
+ // Check conversion indications before doing any heavy work
9575
+ const hasIndicator = find(
9576
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
9577
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9578
+ selectorList, (node) => node.type === CssTreeNodeType.PseudoClassSelector && PSEUDO_ELEMENT_NAMES.has(node.name));
9579
+ if (!hasIndicator) {
9580
+ return createConversionResult(selectorList, false);
9581
+ }
9582
+ // Make a clone of the selector list to avoid modifying the original one,
9583
+ // then convert & return the cloned version
9584
+ const selectorListClone = clone(selectorList);
9585
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
9586
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9163
9587
  walk(selectorListClone, {
9588
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
9589
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9164
9590
  leave: (node) => {
9165
9591
  if (node.type === CssTreeNodeType.PseudoClassSelector) {
9166
- // :after ::after
9167
- // :before ::before
9168
- if (node.name === AFTER || node.name === BEFORE) {
9592
+ // If the pseudo-class is `:before` or `:after`, then we should
9593
+ // convert the node type to pseudo-element:
9594
+ // :after → ::after
9595
+ // :before → ::before
9596
+ if (PSEUDO_ELEMENT_NAMES.has(node.name)) {
9169
9597
  Object.assign(node, {
9170
9598
  ...node,
9171
9599
  type: CssTreeNodeType.PseudoElementSelector,
@@ -9174,7 +9602,7 @@ function convertToPseudoElements(selectorList) {
9174
9602
  }
9175
9603
  },
9176
9604
  });
9177
- return selectorListClone;
9605
+ return createConversionResult(selectorListClone, true);
9178
9606
  }
9179
9607
  /**
9180
9608
  * Converts legacy Extended CSS `matches-css-before` and `matches-css-after`
@@ -9183,33 +9611,36 @@ function convertToPseudoElements(selectorList) {
9183
9611
  * - `:matches-css-after(...)` → `:matches-css(after, ...)`
9184
9612
  *
9185
9613
  * @param node Node to convert
9614
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
9615
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
9616
+ * If the node was not converted, the result will contain the original node with the same object reference
9186
9617
  * @throws If the node is invalid
9187
9618
  */
9188
9619
  function convertLegacyMatchesCss(node) {
9189
- const nodeClone = cloneDeep(node);
9190
- if (nodeClone.type === CssTreeNodeType.PseudoClassSelector
9191
- && [MATCHES_CSS_BEFORE, MATCHES_CSS_AFTER].includes(nodeClone.name)) {
9192
- if (!nodeClone.children || nodeClone.children.size < 1) {
9193
- throw new Error(`Invalid ${nodeClone.name} pseudo-class: missing argument`);
9194
- }
9195
- // Remove the 'matches-css-' prefix to get the direction
9196
- const direction = nodeClone.name.substring(MATCHES_CSS.length + 1);
9197
- // Rename the pseudo-class
9198
- nodeClone.name = MATCHES_CSS;
9199
- // Add the direction to the first raw argument
9200
- const arg = nodeClone.children.first;
9201
- // Check argument
9202
- if (!arg) {
9203
- throw new Error(`Invalid ${nodeClone.name} pseudo-class: argument shouldn't be null`);
9204
- }
9205
- if (arg.type !== CssTreeNodeType.Raw) {
9206
- throw new Error(`Invalid ${nodeClone.name} pseudo-class: unexpected argument type`);
9207
- }
9208
- // Add the direction as the first argument
9209
- arg.value = `${direction},${arg.value}`;
9210
- // Replace the original node with the converted one
9211
- Object.assign(node, nodeClone);
9212
- }
9620
+ // Check conversion indications before doing any heavy work
9621
+ if (node.type !== CssTreeNodeType.PseudoClassSelector || !LEGACY_MATCHES_CSS_NAMES.has(node.name)) {
9622
+ return createConversionResult(node, false);
9623
+ }
9624
+ const nodeClone = clone(node);
9625
+ if (!nodeClone.children || nodeClone.children.length < 1) {
9626
+ throw new Error(`Invalid ${node.name} pseudo-class: missing argument`);
9627
+ }
9628
+ // Rename the pseudo-class
9629
+ nodeClone.name = PseudoClasses.MatchesCss;
9630
+ // Remove the 'matches-css-' prefix to get the direction
9631
+ const direction = node.name.substring(PseudoClasses.MatchesCss.length + 1);
9632
+ // Add the direction to the first raw argument
9633
+ const arg = nodeClone.children[0];
9634
+ // Check argument
9635
+ if (!arg) {
9636
+ throw new Error(`Invalid ${node.name} pseudo-class: argument shouldn't be null`);
9637
+ }
9638
+ if (arg.type !== CssTreeNodeType.Raw) {
9639
+ throw new Error(`Invalid ${node.name} pseudo-class: unexpected argument type`);
9640
+ }
9641
+ // Add the direction as the first argument
9642
+ arg.value = `${direction},${arg.value}`;
9643
+ return createConversionResult(nodeClone, true);
9213
9644
  }
9214
9645
  /**
9215
9646
  * Converts legacy Extended CSS selectors to the modern Extended CSS syntax.
@@ -9219,16 +9650,40 @@ function convertLegacyMatchesCss(node) {
9219
9650
  * - `[-ext-matches-css-before=...]` → `:matches-css(before, ...)`
9220
9651
  *
9221
9652
  * @param selectorList Selector list AST to convert
9222
- * @returns Converted selector list
9653
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
9654
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
9655
+ * If the node was not converted, the result will contain the original node with the same object reference
9223
9656
  */
9224
9657
  function convertFromLegacyExtendedCss(selectorList) {
9225
- // Prepare conversion result
9226
- const selectorListClone = cloneDeep(selectorList);
9658
+ // Check conversion indications before doing any heavy work
9659
+ const hasIndicator = find(
9660
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
9661
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9662
+ selectorList, (node) => {
9663
+ if (node.type === CssTreeNodeType.PseudoClassSelector) {
9664
+ return LEGACY_EXT_CSS_INDICATOR_PSEUDO_NAMES.has(node.name);
9665
+ }
9666
+ if (node.type === CssTreeNodeType.AttributeSelector) {
9667
+ return node.name.name.startsWith(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX);
9668
+ }
9669
+ return false;
9670
+ });
9671
+ if (!hasIndicator) {
9672
+ return createConversionResult(selectorList, false);
9673
+ }
9674
+ const selectorListClone = clone(selectorList);
9675
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
9676
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9227
9677
  walk(selectorListClone, {
9678
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
9679
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9228
9680
  leave: (node) => {
9229
9681
  // :matches-css-before(arg) → :matches-css(before,arg)
9230
9682
  // :matches-css-after(arg) → :matches-css(after,arg)
9231
- convertLegacyMatchesCss(node);
9683
+ const convertedLegacyExtCss = convertLegacyMatchesCss(node);
9684
+ if (convertedLegacyExtCss.isConverted) {
9685
+ Object.assign(node, convertedLegacyExtCss.result);
9686
+ }
9232
9687
  // [-ext-name=...] → :name(...)
9233
9688
  // [-ext-name='...'] → :name(...)
9234
9689
  // [-ext-name="..."] → :name(...)
@@ -9242,7 +9697,7 @@ function convertFromLegacyExtendedCss(selectorList) {
9242
9697
  // Remove the '-ext-' prefix to get the pseudo-class name
9243
9698
  const name = node.name.name.substring(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX.length);
9244
9699
  // Prepare the children list for the pseudo-class node
9245
- const children = new List();
9700
+ const children = [];
9246
9701
  // TODO: Change String node to Raw node to drop the quotes.
9247
9702
  // The structure of the node is the same, just the type
9248
9703
  // is different and generate() will generate the quotes
@@ -9255,7 +9710,7 @@ function convertFromLegacyExtendedCss(selectorList) {
9255
9710
  // For example, if the input is [-ext-has="> .selector"], then
9256
9711
  // we need to parse "> .selector" as a selector instead of string
9257
9712
  // it as a raw value
9258
- if ([HAS, NOT].includes(name)) {
9713
+ if ([PseudoClasses.Has, PseudoClasses.Not].includes(name)) {
9259
9714
  // Get the value of the attribute selector
9260
9715
  const { value } = node;
9261
9716
  // If the value is an identifier, then simply push it to the
@@ -9265,10 +9720,12 @@ function convertFromLegacyExtendedCss(selectorList) {
9265
9720
  }
9266
9721
  else if (value.type === CssTreeNodeType.String) {
9267
9722
  // Parse the value as a selector
9268
- const parsedChildren = CssTree.parse(value.value, CssTreeParserContext.selectorList);
9723
+ const parsedChildren = CssTree.parsePlain(value.value, CssTreeParserContext.selectorList);
9269
9724
  // Don't forget convert the parsed AST again, because
9270
9725
  // it was a raw string before
9271
- children.push(convertFromLegacyExtendedCss(parsedChildren));
9726
+ const convertedChildren = convertFromLegacyExtendedCss(parsedChildren);
9727
+ // Push the converted children to the list
9728
+ children.push(convertedChildren.result);
9272
9729
  }
9273
9730
  }
9274
9731
  else {
@@ -9295,14 +9752,12 @@ function convertFromLegacyExtendedCss(selectorList) {
9295
9752
  children,
9296
9753
  };
9297
9754
  // Handle this case: [-ext-matches-css-before=...] → :matches-css(before,...)
9298
- convertLegacyMatchesCss(pseudoNode);
9299
- // Convert attribute selector to pseudo-class selector, but
9300
- // keep the reference to the original node
9301
- Object.assign(node, pseudoNode);
9755
+ const convertedPseudoNode = convertLegacyMatchesCss(pseudoNode);
9756
+ Object.assign(node, convertedPseudoNode.isConverted ? convertedPseudoNode.result : pseudoNode);
9302
9757
  }
9303
9758
  },
9304
9759
  });
9305
- return selectorListClone;
9760
+ return createConversionResult(selectorListClone, true);
9306
9761
  }
9307
9762
  /**
9308
9763
  * CSS selector converter
@@ -9314,32 +9769,51 @@ class CssSelectorConverter extends ConverterBase {
9314
9769
  * Converts Extended CSS elements to AdGuard-compatible ones
9315
9770
  *
9316
9771
  * @param selectorList Selector list to convert
9317
- * @returns Converted selector list
9772
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
9773
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
9774
+ * If the node was not converted, the result will contain the original node with the same object reference
9318
9775
  * @throws If the rule is invalid or incompatible
9319
9776
  */
9320
9777
  static convertToAdg(selectorList) {
9321
9778
  // First, convert
9322
9779
  // - legacy Extended CSS selectors to the modern Extended CSS syntax and
9323
9780
  // - some pseudo-classes to pseudo-elements
9324
- const selectorListClone = convertToPseudoElements(convertFromLegacyExtendedCss(cloneDeep(selectorList)));
9781
+ const legacyExtCssConverted = convertFromLegacyExtendedCss(selectorList);
9782
+ const pseudoElementsConverted = convertToPseudoElements(legacyExtCssConverted.result);
9783
+ const hasIndicator = legacyExtCssConverted.isConverted || pseudoElementsConverted.isConverted || find(
9784
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
9785
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9786
+ selectorList,
9787
+ // eslint-disable-next-line max-len
9788
+ (node) => node.type === CssTreeNodeType.PseudoClassSelector && CSS_CONVERSION_INDICATOR_PSEUDO_NAMES.has(node.name));
9789
+ if (!hasIndicator) {
9790
+ return createConversionResult(selectorList, false);
9791
+ }
9792
+ const selectorListClone = legacyExtCssConverted.isConverted || pseudoElementsConverted.isConverted
9793
+ ? pseudoElementsConverted.result
9794
+ : clone(selectorList);
9325
9795
  // Then, convert some Extended CSS pseudo-classes to AdGuard-compatible ones
9796
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
9797
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9326
9798
  walk(selectorListClone, {
9799
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
9800
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9327
9801
  leave: (node) => {
9328
9802
  if (node.type === CssTreeNodeType.PseudoClassSelector) {
9329
9803
  // :-abp-contains(...) → :contains(...)
9330
9804
  // :has-text(...) → :contains(...)
9331
- if (node.name === ABP_CONTAINS || node.name === HAS_TEXT) {
9332
- CssTree.renamePseudoClass(node, CONTAINS);
9805
+ if (node.name === PseudoClasses.AbpContains || node.name === PseudoClasses.HasText) {
9806
+ CssTree.renamePseudoClass(node, PseudoClasses.Contains);
9333
9807
  }
9334
9808
  // :-abp-has(...) → :has(...)
9335
- if (node.name === ABP_HAS) {
9336
- CssTree.renamePseudoClass(node, HAS);
9809
+ if (node.name === PseudoClasses.AbpHas) {
9810
+ CssTree.renamePseudoClass(node, PseudoClasses.Has);
9337
9811
  }
9338
9812
  // TODO: check uBO's `:others()` and `:watch-attr()` pseudo-classes
9339
9813
  }
9340
9814
  },
9341
9815
  });
9342
- return selectorListClone;
9816
+ return createConversionResult(selectorListClone, true);
9343
9817
  }
9344
9818
  }
9345
9819
 
@@ -9356,27 +9830,39 @@ class CssInjectionRuleConverter extends RuleConverterBase {
9356
9830
  * Converts a CSS injection rule to AdGuard format, if possible.
9357
9831
  *
9358
9832
  * @param rule Rule node to convert
9359
- * @returns Array of converted rule nodes
9833
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
9834
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
9835
+ * If the rule was not converted, the result array will contain the original node with the same object reference
9360
9836
  * @throws If the rule is invalid or cannot be converted
9361
9837
  */
9362
9838
  static convertToAdg(rule) {
9363
- // Clone the provided AST node to avoid side effects
9364
- const ruleNode = cloneDeep(rule);
9839
+ const separator = rule.separator.value;
9840
+ let convertedSeparator = separator;
9365
9841
  // Change the separator if the rule contains ExtendedCSS selectors
9366
- if (CssTree.hasAnySelectorExtendedCssNode(ruleNode.body.selectorList) || ruleNode.body.remove) {
9367
- ruleNode.separator.value = ruleNode.exception
9842
+ if (CssTree.hasAnySelectorExtendedCssNode(rule.body.selectorList) || rule.body.remove) {
9843
+ convertedSeparator = rule.exception
9368
9844
  ? CosmeticRuleSeparator.AdgExtendedCssInjectionException
9369
9845
  : CosmeticRuleSeparator.AdgExtendedCssInjection;
9370
9846
  }
9371
9847
  else {
9372
- ruleNode.separator.value = ruleNode.exception
9848
+ convertedSeparator = rule.exception
9373
9849
  ? CosmeticRuleSeparator.AdgCssInjectionException
9374
9850
  : CosmeticRuleSeparator.AdgCssInjection;
9375
9851
  }
9376
- // Convert CSS selector list
9377
- Object.assign(ruleNode.body.selectorList, CssSelectorConverter.convertToAdg(fromPlainObject(ruleNode.body.selectorList)));
9378
- ruleNode.syntax = AdblockSyntax.Adg;
9379
- return [ruleNode];
9852
+ const convertedSelectorList = CssSelectorConverter.convertToAdg(rule.body.selectorList);
9853
+ // Check if the rule needs to be converted
9854
+ if (!(rule.syntax === AdblockSyntax.Common || rule.syntax === AdblockSyntax.Adg)
9855
+ || separator !== convertedSeparator
9856
+ || convertedSelectorList.isConverted) {
9857
+ // TODO: Replace with custom clone method
9858
+ const ruleClone = clone(rule);
9859
+ ruleClone.syntax = AdblockSyntax.Adg;
9860
+ ruleClone.separator.value = convertedSeparator;
9861
+ ruleClone.body.selectorList = convertedSelectorList.result;
9862
+ return createNodeConversionResult([ruleClone], true);
9863
+ }
9864
+ // Otherwise, return the original rule
9865
+ return createNodeConversionResult([rule], false);
9380
9866
  }
9381
9867
  }
9382
9868
 
@@ -9393,27 +9879,39 @@ class ElementHidingRuleConverter extends RuleConverterBase {
9393
9879
  * Converts an element hiding rule to AdGuard format, if possible.
9394
9880
  *
9395
9881
  * @param rule Rule node to convert
9396
- * @returns Array of converted rule nodes
9882
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
9883
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
9884
+ * If the rule was not converted, the result array will contain the original node with the same object reference
9397
9885
  * @throws If the rule is invalid or cannot be converted
9398
9886
  */
9399
9887
  static convertToAdg(rule) {
9400
- // Clone the provided AST node to avoid side effects
9401
- const ruleNode = cloneDeep(rule);
9888
+ const separator = rule.separator.value;
9889
+ let convertedSeparator = separator;
9402
9890
  // Change the separator if the rule contains ExtendedCSS selectors
9403
- if (CssTree.hasAnySelectorExtendedCssNode(ruleNode.body.selectorList)) {
9404
- ruleNode.separator.value = ruleNode.exception
9891
+ if (CssTree.hasAnySelectorExtendedCssNode(rule.body.selectorList)) {
9892
+ convertedSeparator = rule.exception
9405
9893
  ? CosmeticRuleSeparator.ExtendedElementHidingException
9406
9894
  : CosmeticRuleSeparator.ExtendedElementHiding;
9407
9895
  }
9408
9896
  else {
9409
- ruleNode.separator.value = ruleNode.exception
9897
+ convertedSeparator = rule.exception
9410
9898
  ? CosmeticRuleSeparator.ElementHidingException
9411
9899
  : CosmeticRuleSeparator.ElementHiding;
9412
9900
  }
9413
- // Convert CSS selector list
9414
- Object.assign(ruleNode.body.selectorList, CssSelectorConverter.convertToAdg(fromPlainObject(ruleNode.body.selectorList)));
9415
- ruleNode.syntax = AdblockSyntax.Adg;
9416
- return [ruleNode];
9901
+ const convertedSelectorList = CssSelectorConverter.convertToAdg(rule.body.selectorList);
9902
+ // Check if the rule needs to be converted
9903
+ if (!(rule.syntax === AdblockSyntax.Common || rule.syntax === AdblockSyntax.Adg)
9904
+ || separator !== convertedSeparator
9905
+ || convertedSelectorList.isConverted) {
9906
+ // TODO: Replace with custom clone method
9907
+ const ruleClone = clone(rule);
9908
+ ruleClone.syntax = AdblockSyntax.Adg;
9909
+ ruleClone.separator.value = convertedSeparator;
9910
+ ruleClone.body.selectorList = convertedSelectorList.result;
9911
+ return createNodeConversionResult([ruleClone], true);
9912
+ }
9913
+ // Otherwise, return the original rule
9914
+ return createNodeConversionResult([rule], false);
9417
9915
  }
9418
9916
  }
9419
9917
 
@@ -9441,7 +9939,7 @@ function createNetworkRuleNode(pattern, modifiers = undefined, exception = false
9441
9939
  },
9442
9940
  };
9443
9941
  if (!isUndefined(modifiers)) {
9444
- result.modifiers = cloneDeep(modifiers);
9942
+ result.modifiers = clone(modifiers);
9445
9943
  }
9446
9944
  return result;
9447
9945
  }
@@ -9461,32 +9959,37 @@ class HeaderRemovalRuleConverter extends RuleConverterBase {
9461
9959
  * Converts a header removal rule to AdGuard syntax, if possible.
9462
9960
  *
9463
9961
  * @param rule Rule node to convert
9464
- * @returns Array of converted rule nodes
9962
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
9963
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
9964
+ * If the rule was not converted, the result array will contain the original node with the same object reference
9465
9965
  * @throws If the rule is invalid or cannot be converted
9966
+ * @example
9967
+ * If the input rule is:
9968
+ * ```adblock
9969
+ * example.com##^responseheader(header-name)
9970
+ * ```
9971
+ * The output will be:
9972
+ * ```adblock
9973
+ * ||example.com^$removeheader=header-name
9974
+ * ```
9466
9975
  */
9467
9976
  static convertToAdg(rule) {
9468
- // Clone the provided AST node to avoid side effects
9469
- const ruleNode = cloneDeep(rule);
9470
9977
  // TODO: Add support for ABP syntax once it starts supporting header removal rules
9471
- // Check the input rule
9472
- if (ruleNode.category !== RuleCategory.Cosmetic
9473
- || ruleNode.type !== CosmeticRuleType.HtmlFilteringRule
9474
- || ruleNode.body.body.type !== CssTreeNodeType.Function
9475
- || ruleNode.body.body.name !== UBO_RESPONSEHEADER_MARKER) {
9476
- throw new RuleConversionError('Not a response header rule');
9978
+ // Leave the rule as is if it's not a header removal rule
9979
+ if (rule.category !== RuleCategory.Cosmetic
9980
+ || rule.type !== CosmeticRuleType.HtmlFilteringRule
9981
+ || rule.body.body.type !== CssTreeNodeType.Function
9982
+ || rule.body.body.name !== UBO_RESPONSEHEADER_MARKER) {
9983
+ return createNodeConversionResult([rule], false);
9477
9984
  }
9478
9985
  // Prepare network rule pattern
9479
- let pattern = EMPTY;
9480
- if (ruleNode.domains.children.length === 1) {
9986
+ const pattern = [];
9987
+ if (rule.domains.children.length === 1) {
9481
9988
  // If the rule has only one domain, we can use a simple network rule pattern:
9482
9989
  // ||single-domain-from-the-rule^
9483
- pattern = [
9484
- ADBLOCK_URL_START,
9485
- ruleNode.domains.children[0].value,
9486
- ADBLOCK_URL_SEPARATOR,
9487
- ].join(EMPTY);
9990
+ pattern.push(ADBLOCK_URL_START, rule.domains.children[0].value, ADBLOCK_URL_SEPARATOR);
9488
9991
  }
9489
- else if (ruleNode.domains.children.length > 1) {
9992
+ else if (rule.domains.children.length > 1) {
9490
9993
  // TODO: Add support for multiple domains, for example:
9491
9994
  // example.com,example.org,example.net##^responseheader(header-name)
9492
9995
  // We should consider allowing $domain with $removeheader modifier,
@@ -9496,13 +9999,13 @@ class HeaderRemovalRuleConverter extends RuleConverterBase {
9496
9999
  }
9497
10000
  // Prepare network rule modifiers
9498
10001
  const modifiers = createModifierListNode();
9499
- modifiers.children.push(createModifierNode(ADG_REMOVEHEADER_MODIFIER, CssTree.generateFunctionValue(fromPlainObject(ruleNode.body.body))));
10002
+ modifiers.children.push(createModifierNode(ADG_REMOVEHEADER_MODIFIER, CssTree.generateFunctionPlainValue(rule.body.body)));
9500
10003
  // Construct the network rule
9501
- return [
9502
- createNetworkRuleNode(pattern, modifiers,
10004
+ return createNodeConversionResult([
10005
+ createNetworkRuleNode(pattern.join(EMPTY), modifiers,
9503
10006
  // Copy the exception flag
9504
- ruleNode.exception, AdblockSyntax.Adg),
9505
- ];
10007
+ rule.exception, AdblockSyntax.Adg),
10008
+ ], true);
9506
10009
  }
9507
10010
  }
9508
10011
 
@@ -9519,48 +10022,69 @@ class CosmeticRuleConverter extends RuleConverterBase {
9519
10022
  * Converts a cosmetic rule to AdGuard syntax, if possible.
9520
10023
  *
9521
10024
  * @param rule Rule node to convert
9522
- * @returns Array of converted rule nodes
10025
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
10026
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
10027
+ * If the rule was not converted, the result array will contain the original node with the same object reference
9523
10028
  * @throws If the rule is invalid or cannot be converted
9524
10029
  */
9525
10030
  static convertToAdg(rule) {
9526
- // Clone the provided AST node to avoid side effects
9527
- const ruleNode = cloneDeep(rule);
9528
- // Convert cosmetic rule modifiers
9529
- if (ruleNode.modifiers) {
9530
- if (ruleNode.syntax === AdblockSyntax.Ubo) {
9531
- // uBO doesn't support this rule:
9532
- // example.com##+js(set-constant.js, foo, bar):matches-path(/baz)
9533
- if (ruleNode.type === CosmeticRuleType.ScriptletInjectionRule) {
9534
- throw new RuleConversionError('uBO scriptlet injection rules don\'t support cosmetic rule modifiers');
9535
- }
9536
- ruleNode.modifiers = AdgCosmeticRuleModifierConverter.convertFromUbo(ruleNode.modifiers);
9537
- }
9538
- else if (ruleNode.syntax === AdblockSyntax.Abp) {
9539
- // TODO: Implement once ABP starts supporting cosmetic rule modifiers
9540
- throw new RuleConversionError('ABP don\'t support cosmetic rule modifiers');
9541
- }
9542
- }
10031
+ let subconverterResult;
9543
10032
  // Convert cosmetic rule based on its type
9544
- switch (ruleNode.type) {
10033
+ switch (rule.type) {
9545
10034
  case CosmeticRuleType.ElementHidingRule:
9546
- return ElementHidingRuleConverter.convertToAdg(ruleNode);
10035
+ subconverterResult = ElementHidingRuleConverter.convertToAdg(rule);
10036
+ break;
9547
10037
  case CosmeticRuleType.ScriptletInjectionRule:
9548
- return ScriptletRuleConverter.convertToAdg(ruleNode);
10038
+ subconverterResult = ScriptletRuleConverter.convertToAdg(rule);
10039
+ break;
9549
10040
  case CosmeticRuleType.CssInjectionRule:
9550
- return CssInjectionRuleConverter.convertToAdg(ruleNode);
10041
+ subconverterResult = CssInjectionRuleConverter.convertToAdg(rule);
10042
+ break;
9551
10043
  case CosmeticRuleType.HtmlFilteringRule:
9552
10044
  // Handle special case: uBO response header filtering rule
9553
- if (ruleNode.body.body.type === CssTreeNodeType.Function
9554
- && ruleNode.body.body.name === UBO_RESPONSEHEADER_MARKER) {
9555
- return HeaderRemovalRuleConverter.convertToAdg(ruleNode);
10045
+ if (rule.body.body.type === CssTreeNodeType.Function
10046
+ && rule.body.body.name === UBO_RESPONSEHEADER_MARKER) {
10047
+ subconverterResult = HeaderRemovalRuleConverter.convertToAdg(rule);
10048
+ }
10049
+ else {
10050
+ subconverterResult = HtmlRuleConverter.convertToAdg(rule);
9556
10051
  }
9557
- return HtmlRuleConverter.convertToAdg(ruleNode);
9558
- // Note: Currently, only ADG supports JS injection rules
10052
+ break;
10053
+ // Note: Currently, only ADG supports JS injection rules, so we don't need to convert them
9559
10054
  case CosmeticRuleType.JsInjectionRule:
9560
- return [ruleNode];
10055
+ subconverterResult = createNodeConversionResult([rule], false);
10056
+ break;
9561
10057
  default:
9562
10058
  throw new RuleConversionError('Unsupported cosmetic rule type');
9563
10059
  }
10060
+ let convertedModifiers;
10061
+ // Convert cosmetic rule modifiers, if any
10062
+ if (rule.modifiers) {
10063
+ if (rule.syntax === AdblockSyntax.Ubo) {
10064
+ // uBO doesn't support this rule:
10065
+ // example.com##+js(set-constant.js, foo, bar):matches-path(/baz)
10066
+ if (rule.type === CosmeticRuleType.ScriptletInjectionRule) {
10067
+ throw new RuleConversionError('uBO scriptlet injection rules don\'t support cosmetic rule modifiers');
10068
+ }
10069
+ convertedModifiers = AdgCosmeticRuleModifierConverter.convertFromUbo(rule.modifiers);
10070
+ }
10071
+ else if (rule.syntax === AdblockSyntax.Abp) {
10072
+ // TODO: Implement once ABP starts supporting cosmetic rule modifiers
10073
+ throw new RuleConversionError('ABP don\'t support cosmetic rule modifiers');
10074
+ }
10075
+ }
10076
+ if ((subconverterResult.result.length > 1 || subconverterResult.isConverted)
10077
+ || (convertedModifiers && convertedModifiers.isConverted)) {
10078
+ // Add modifier list to the subconverter result rules
10079
+ subconverterResult.result.forEach((subconverterRule) => {
10080
+ if (convertedModifiers && subconverterRule.category === RuleCategory.Cosmetic) {
10081
+ // eslint-disable-next-line no-param-reassign
10082
+ subconverterRule.modifiers = convertedModifiers.result;
10083
+ }
10084
+ });
10085
+ return subconverterResult;
10086
+ }
10087
+ return createNodeConversionResult([rule], false);
9564
10088
  }
9565
10089
  }
9566
10090
 
@@ -9632,17 +10156,16 @@ class NetworkRuleModifierListConverter extends ConverterBase {
9632
10156
  * Converts a network rule modifier list to AdGuard format, if possible.
9633
10157
  *
9634
10158
  * @param modifierList Network rule modifier list node to convert
9635
- * @returns Converted modifier list node
10159
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
10160
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
10161
+ * If the node was not converted, the result will contain the original node with the same object reference
9636
10162
  * @throws If the conversion is not possible
9637
10163
  */
9638
10164
  static convertToAdg(modifierList) {
9639
- // Clone the provided AST node to avoid side effects
9640
- const modifierListNode = cloneDeep(modifierList);
9641
- const convertedModifierList = createModifierListNode();
9642
- // We should merge $csp modifiers into one
9643
- const cspValues = [];
9644
- modifierListNode.children.forEach((modifierNode) => {
9645
- // Handle regular modifiers conversion and $csp modifiers collection
10165
+ const conversionMap = new MultiValueMap();
10166
+ // Special case: $csp modifier
10167
+ let cspCount = 0;
10168
+ modifierList.children.forEach((modifierNode, index) => {
9646
10169
  const modifierConversions = ADG_CONVERSION_MAP.get(modifierNode.modifier.value);
9647
10170
  if (modifierConversions) {
9648
10171
  for (const modifierConversion of modifierConversions) {
@@ -9655,17 +10178,14 @@ class NetworkRuleModifierListConverter extends ConverterBase {
9655
10178
  const value = modifierConversion.value
9656
10179
  ? modifierConversion.value(modifierNode.value?.value)
9657
10180
  : modifierNode.value?.value;
9658
- if (name === CSP_MODIFIER && value) {
9659
- // Special case: collect $csp values
9660
- cspValues.push(value);
10181
+ // Check if the name or the value is different from the original modifier
10182
+ // If so, add the converted modifier to the list
10183
+ if (name !== modifierNode.modifier.value || value !== modifierNode.value?.value) {
10184
+ conversionMap.add(index, createModifierNode(name, value, exception));
9661
10185
  }
9662
- else {
9663
- // Regular case: collect the converted modifiers, if the modifier list
9664
- // not already contains the same modifier
9665
- const existingModifier = convertedModifierList.children.find((m) => m.modifier.value === name && m.exception === exception && m.value?.value === value);
9666
- if (!existingModifier) {
9667
- convertedModifierList.children.push(createModifierNode(name, value, exception));
9668
- }
10186
+ // Special case: $csp modifier
10187
+ if (name === CSP_MODIFIER) {
10188
+ cspCount += 1;
9669
10189
  }
9670
10190
  }
9671
10191
  return;
@@ -9688,26 +10208,52 @@ class NetworkRuleModifierListConverter extends ConverterBase {
9688
10208
  // Try to convert the redirect resource name to ADG format
9689
10209
  // This function returns undefined if the resource name is unknown
9690
10210
  const convertedRedirectResource = redirects.convertRedirectNameToAdg(redirectResource);
9691
- convertedModifierList.children.push(createModifierNode(modifierName,
9692
- // If the redirect resource name is unknown, fall back to the original one
9693
- // Later, the validator will throw an error if the resource name is invalid
9694
- convertedRedirectResource || redirectResource, modifierNode.exception));
9695
- return;
9696
- }
9697
- // In all other cases, just copy the modifier as is, if the modifier list
9698
- // not already contains the same modifier
9699
- const existingModifier = convertedModifierList.children.find((m) => m.modifier.value === modifierNode.modifier.value
9700
- && m.exception === modifierNode.exception
9701
- && m.value?.value === modifierNode.value?.value);
9702
- if (!existingModifier) {
9703
- convertedModifierList.children.push(modifierNode);
10211
+ // Check if the modifier name or the redirect resource name is different from the original modifier
10212
+ // If so, add the converted modifier to the list
10213
+ if (modifierName !== modifierNode.modifier.value
10214
+ || (convertedRedirectResource !== undefined && convertedRedirectResource !== redirectResource)) {
10215
+ conversionMap.add(index, createModifierNode(modifierName,
10216
+ // If the redirect resource name is unknown, fall back to the original one
10217
+ // Later, the validator will throw an error if the resource name is invalid
10218
+ convertedRedirectResource || redirectResource, modifierNode.exception));
10219
+ }
9704
10220
  }
9705
10221
  });
9706
- // Merge $csp modifiers into one, then add it to the converted modifier list
9707
- if (cspValues.length > 0) {
9708
- convertedModifierList.children.push(createModifierNode(CSP_MODIFIER, cspValues.join(CSP_SEPARATOR)));
10222
+ // Prepare the result if there are any converted modifiers or $csp modifiers
10223
+ if (conversionMap.size || cspCount) {
10224
+ const modifierListClone = cloneModifierListNode(modifierList);
10225
+ // Replace the original modifiers with the converted ones
10226
+ // One modifier may be replaced with multiple modifiers, so we need to flatten the array
10227
+ modifierListClone.children = modifierListClone.children.map((modifierNode, index) => {
10228
+ const conversionRecord = conversionMap.get(index);
10229
+ if (conversionRecord) {
10230
+ return conversionRecord;
10231
+ }
10232
+ return modifierNode;
10233
+ }).flat();
10234
+ // Special case: $csp modifier: merge multiple $csp modifiers into one
10235
+ // and put it at the end of the modifier list
10236
+ if (cspCount) {
10237
+ const cspValues = [];
10238
+ modifierListClone.children = modifierListClone.children.filter((modifierNode) => {
10239
+ if (modifierNode.modifier.value === CSP_MODIFIER) {
10240
+ if (!modifierNode.value?.value) {
10241
+ throw new RuleConversionError('$csp modifier value is missing');
10242
+ }
10243
+ cspValues.push(modifierNode.value?.value);
10244
+ return false;
10245
+ }
10246
+ return true;
10247
+ });
10248
+ modifierListClone.children.push(createModifierNode(CSP_MODIFIER, cspValues.join(CSP_SEPARATOR)));
10249
+ }
10250
+ // Before returning the result, remove duplicated modifiers
10251
+ modifierListClone.children = modifierListClone.children.filter((modifierNode, index, self) => self.findIndex((m) => m.modifier.value === modifierNode.modifier.value
10252
+ && m.exception === modifierNode.exception
10253
+ && m.value?.value === modifierNode.value?.value) === index);
10254
+ return createConversionResult(modifierListClone, true);
9709
10255
  }
9710
- return convertedModifierList;
10256
+ return createConversionResult(modifierList, false);
9711
10257
  }
9712
10258
  }
9713
10259
 
@@ -9724,17 +10270,35 @@ class NetworkRuleConverter extends RuleConverterBase {
9724
10270
  * Converts a network rule to AdGuard format, if possible.
9725
10271
  *
9726
10272
  * @param rule Rule node to convert
9727
- * @returns Array of converted rule nodes
10273
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
10274
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
10275
+ * If the rule was not converted, the result array will contain the original node with the same object reference
9728
10276
  * @throws If the rule is invalid or cannot be converted
9729
10277
  */
9730
10278
  static convertToAdg(rule) {
9731
- // Clone the provided AST node to avoid side effects
9732
- const ruleNode = cloneDeep(rule);
9733
- // Convert modifiers
9734
- if (ruleNode.modifiers) {
9735
- Object.assign(ruleNode.modifiers, NetworkRuleModifierListConverter.convertToAdg(ruleNode.modifiers));
10279
+ if (rule.modifiers) {
10280
+ const modifiers = NetworkRuleModifierListConverter.convertToAdg(rule.modifiers);
10281
+ // If the object reference is different, it means that the modifiers were converted
10282
+ // In this case, we should clone the entire rule and replace the modifiers with the converted ones
10283
+ if (modifiers.isConverted) {
10284
+ return {
10285
+ result: [{
10286
+ category: RuleCategory.Network,
10287
+ type: 'NetworkRule',
10288
+ syntax: rule.syntax,
10289
+ exception: rule.exception,
10290
+ pattern: {
10291
+ type: 'Value',
10292
+ value: rule.pattern.value,
10293
+ },
10294
+ modifiers: modifiers.result,
10295
+ }],
10296
+ isConverted: true,
10297
+ };
10298
+ }
9736
10299
  }
9737
- return [ruleNode];
10300
+ // If the modifiers were not converted, return the original rule
10301
+ return createNodeConversionResult([rule], false);
9738
10302
  }
9739
10303
  }
9740
10304
 
@@ -9755,48 +10319,27 @@ class RuleConverter extends RuleConverterBase {
9755
10319
  * Converts an adblock filtering rule to AdGuard format, if possible.
9756
10320
  *
9757
10321
  * @param rule Rule node to convert
9758
- * @returns Array of converted rule nodes
10322
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
10323
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
10324
+ * If the rule was not converted, the result array will contain the original node with the same object reference
9759
10325
  * @throws If the rule is invalid or cannot be converted
9760
10326
  */
9761
10327
  static convertToAdg(rule) {
9762
- // Clone the provided AST node to avoid side effects
9763
- const ruleNode = cloneDeep(rule);
9764
10328
  // Delegate conversion to the corresponding sub-converter
9765
10329
  // based on the rule category
9766
- switch (ruleNode.category) {
10330
+ switch (rule.category) {
9767
10331
  case RuleCategory.Comment:
9768
- return CommentRuleConverter.convertToAdg(ruleNode);
10332
+ return CommentRuleConverter.convertToAdg(rule);
9769
10333
  case RuleCategory.Cosmetic:
9770
- return CosmeticRuleConverter.convertToAdg(ruleNode);
10334
+ return CosmeticRuleConverter.convertToAdg(rule);
9771
10335
  case RuleCategory.Network:
9772
- return NetworkRuleConverter.convertToAdg(ruleNode);
10336
+ return NetworkRuleConverter.convertToAdg(rule);
9773
10337
  default:
9774
- throw new RuleConversionError(`Unknown rule category: ${ruleNode.category}`);
10338
+ throw new RuleConversionError(`Unknown rule category: ${rule.category}`);
9775
10339
  }
9776
10340
  }
9777
10341
  }
9778
10342
 
9779
- /**
9780
- * @file Utility functions for working with filter list nodes
9781
- */
9782
- /**
9783
- * Creates a filter list node
9784
- *
9785
- * @param rules Rules to put in the list (optional, defaults to an empty list)
9786
- * @returns Filter list node
9787
- */
9788
- function createFilterListNode(rules = []) {
9789
- const result = {
9790
- type: 'FilterList',
9791
- children: [],
9792
- };
9793
- // We need to clone the rules to avoid side effects
9794
- if (rules.length > 0) {
9795
- result.children = cloneDeep(rules);
9796
- }
9797
- return result;
9798
- }
9799
-
9800
10343
  /**
9801
10344
  * @file Adblock filter list converter
9802
10345
  */
@@ -9815,18 +10358,133 @@ class FilterListConverter extends ConverterBase {
9815
10358
  * Converts an adblock filter list to AdGuard format, if possible.
9816
10359
  *
9817
10360
  * @param filterListNode Filter list node to convert
9818
- * @returns Converted filter list node
9819
- * @throws If the filter list is invalid or cannot be converted
10361
+ * @param tolerant Indicates whether the converter should be tolerant to invalid rules. If enabled and a rule is
10362
+ * invalid, it will be left as is. If disabled and a rule is invalid, the whole filter list will be failed.
10363
+ * Defaults to `true`.
10364
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
10365
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
10366
+ * If the node was not converted, the result will contain the original node with the same object reference
10367
+ * @throws If the filter list is invalid or cannot be converted (if the tolerant mode is disabled)
10368
+ */
10369
+ static convertToAdg(filterListNode, tolerant = true) {
10370
+ // Prepare a map to store the converted rules by their index in the filter list
10371
+ const conversionMap = new MultiValueMap();
10372
+ // Iterate over the filtering rules and convert them one by one, then add them to the result (one conversion may
10373
+ // result in multiple rules)
10374
+ for (let i = 0; i < filterListNode.children.length; i += 1) {
10375
+ try {
10376
+ const convertedRules = RuleConverter.convertToAdg(filterListNode.children[i]);
10377
+ // Add the converted rules to the map if they were converted
10378
+ if (convertedRules.isConverted) {
10379
+ conversionMap.add(i, ...convertedRules.result);
10380
+ }
10381
+ }
10382
+ catch (error) {
10383
+ // If the tolerant mode is disabled, we should throw an error, this will fail the whole filter list
10384
+ // conversion.
10385
+ // Otherwise, we just ignore the error and leave the rule as is
10386
+ if (!tolerant) {
10387
+ throw error;
10388
+ }
10389
+ }
10390
+ }
10391
+ // If the conversion map is empty, it means that no rules were converted, so we can return the original filter
10392
+ // list
10393
+ if (conversionMap.size === 0) {
10394
+ return createConversionResult(filterListNode, false);
10395
+ }
10396
+ // Otherwise, create a new filter list node with the converted rules
10397
+ const convertedFilterList = {
10398
+ type: 'FilterList',
10399
+ children: [],
10400
+ };
10401
+ // Iterate over the original rules again and add them to the converted filter list, replacing the converted
10402
+ // rules with the new ones at the specified indexes
10403
+ for (let i = 0; i < filterListNode.children.length; i += 1) {
10404
+ const rules = conversionMap.get(i);
10405
+ if (rules) {
10406
+ convertedFilterList.children.push(...rules);
10407
+ }
10408
+ else {
10409
+ // We clone the unconverted rules to avoid mutating the original filter list if we return the converted
10410
+ // one
10411
+ convertedFilterList.children.push(clone(filterListNode.children[i]));
10412
+ }
10413
+ }
10414
+ return createConversionResult(convertedFilterList, true);
10415
+ }
10416
+ }
10417
+
10418
+ /**
10419
+ * @file Filter list converter for raw filter lists
10420
+ *
10421
+ * Technically, this is a wrapper around `FilterListConverter` that works with nodes instead of strings.
10422
+ */
10423
+ /**
10424
+ * Adblock filter list converter class.
10425
+ *
10426
+ * You can use this class to convert string-based filter lists, since most of the converters work with nodes.
10427
+ * This class just provides an extra layer on top of the {@link FilterListConverter} and calls the parser/serializer
10428
+ * before/after the conversion internally.
10429
+ *
10430
+ * @todo Implement `convertToUbo` and `convertToAbp`
10431
+ */
10432
+ class RawFilterListConverter extends ConverterBase {
10433
+ /**
10434
+ * Converts an adblock filter list text to AdGuard format, if possible.
10435
+ *
10436
+ * @param rawFilterList Raw filter list text to convert
10437
+ * @param tolerant Indicates whether the converter should be tolerant to invalid rules. If enabled and a rule is
10438
+ * invalid, it will be left as is. If disabled and a rule is invalid, the whole filter list will be failed.
10439
+ * Defaults to `true`.
10440
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
10441
+ * the array of converted filter list text, and its `isConverted` flag indicates whether the original rule was
10442
+ * converted. If the rule was not converted, the original filter list text will be returned
10443
+ * @throws If the filter list is invalid or cannot be converted (if the tolerant mode is disabled)
9820
10444
  */
9821
- static convertToAdg(filterListNode) {
9822
- const result = createFilterListNode();
9823
- // Iterate over the filtering rules and convert them one by one,
9824
- // then add them to the result (one conversion may result in multiple rules)
9825
- for (const ruleNode of filterListNode.children) {
9826
- const convertedRules = RuleConverter.convertToAdg(ruleNode);
9827
- result.children.push(...convertedRules);
10445
+ static convertToAdg(rawFilterList, tolerant = true) {
10446
+ const conversionResult = FilterListConverter.convertToAdg(FilterListParser.parse(rawFilterList, tolerant), tolerant);
10447
+ // If the filter list was not converted, return the original text
10448
+ if (!conversionResult.isConverted) {
10449
+ return createConversionResult(rawFilterList, false);
9828
10450
  }
9829
- return result;
10451
+ // Otherwise, serialize the filter list and return the result
10452
+ return createConversionResult(FilterListParser.generate(conversionResult.result), true);
10453
+ }
10454
+ }
10455
+
10456
+ /**
10457
+ * @file Rule converter for raw rules
10458
+ *
10459
+ * Technically, this is a wrapper around `RuleConverter` that works with nodes instead of strings.
10460
+ */
10461
+ /**
10462
+ * Adblock filtering rule converter class.
10463
+ *
10464
+ * You can use this class to convert string-based adblock rules, since most of the converters work with nodes.
10465
+ * This class just provides an extra layer on top of the {@link RuleConverter} and calls the parser/serializer
10466
+ * before/after the conversion internally.
10467
+ *
10468
+ * @todo Implement `convertToUbo` and `convertToAbp`
10469
+ */
10470
+ class RawRuleConverter extends ConverterBase {
10471
+ /**
10472
+ * Converts an adblock filtering rule to AdGuard format, if possible.
10473
+ *
10474
+ * @param rawRule Raw rule text to convert
10475
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
10476
+ * the array of converted rule texts, and its `isConverted` flag indicates whether the original rule was converted.
10477
+ * If the rule was not converted, the original rule text will be returned
10478
+ * @throws If the rule is invalid or cannot be converted
10479
+ */
10480
+ static convertToAdg(rawRule) {
10481
+ const conversionResult = RuleConverter.convertToAdg(RuleParser.parse(rawRule));
10482
+ // If the rule was not converted, return the original rule text
10483
+ if (!conversionResult.isConverted) {
10484
+ return createConversionResult([rawRule], false);
10485
+ }
10486
+ // Otherwise, serialize the converted rule nodes
10487
+ return createConversionResult(conversionResult.result.map(RuleParser.generate), true);
9830
10488
  }
9831
10489
  }
9832
10490
 
@@ -9908,7 +10566,7 @@ class LogicalExpressionUtils {
9908
10566
  }
9909
10567
  }
9910
10568
 
9911
- const version$1 = "1.1.5";
10569
+ const version$1 = "1.1.7";
9912
10570
 
9913
10571
  /**
9914
10572
  * @file AGTree version
@@ -9919,4 +10577,4 @@ const version$1 = "1.1.5";
9919
10577
  // with wrong relative path to `package.json`. So we need this little "hack"
9920
10578
  const version = version$1;
9921
10579
 
9922
- export { ADBLOCK_URL_SEPARATOR, ADBLOCK_URL_SEPARATOR_REGEX, ADBLOCK_URL_START, ADBLOCK_URL_START_REGEX, ADBLOCK_WILDCARD, ADBLOCK_WILDCARD_REGEX, ADG_SCRIPTLET_MASK, AGLINT_COMMAND_PREFIX, AdblockSyntax, AdblockSyntaxError, AgentCommentRuleParser, AgentParser, AppListParser, COMMA_DOMAIN_LIST_SEPARATOR, CommentMarker, CommentRuleParser, CommentRuleType, ConfigCommentRuleParser, CosmeticRuleParser, CosmeticRuleSeparator, CosmeticRuleSeparatorUtils, CosmeticRuleType, CssTree, CssTreeNodeType, CssTreeParserContext, DomainListParser, DomainUtils, EXT_CSS_LEGACY_ATTRIBUTES, EXT_CSS_PSEUDO_CLASSES, FORBIDDEN_CSS_FUNCTIONS, FilterListConverter, FilterListParser, HINT_MARKER, HintCommentRuleParser, HintParser, IF, INCLUDE, LogicalExpressionParser, LogicalExpressionUtils, METADATA_HEADERS, MODIFIERS_SEPARATOR, MODIFIER_ASSIGN_OPERATOR, MetadataCommentRuleParser, MethodListParser, ModifierListParser, ModifierParser, NEGATION_MARKER, NETWORK_RULE_EXCEPTION_MARKER, NETWORK_RULE_SEPARATOR, NetworkRuleParser, NotImplementedError, PIPE_MODIFIER_SEPARATOR, PREPROCESSOR_MARKER, ParameterListParser, PreProcessorCommentRuleParser, QuoteType, QuoteUtils, RegExpUtils, RuleCategory, RuleConversionError, RuleConverter, RuleParser, SAFARI_CB_AFFINITY, SPECIAL_REGEX_SYMBOLS, StealthOptionListParser, UBO_SCRIPTLET_MASK, locRange, modifierValidator, shiftLoc, version };
10580
+ export { ADBLOCK_URL_SEPARATOR, ADBLOCK_URL_SEPARATOR_REGEX, ADBLOCK_URL_START, ADBLOCK_URL_START_REGEX, ADBLOCK_WILDCARD, ADBLOCK_WILDCARD_REGEX, ADG_SCRIPTLET_MASK, AGLINT_COMMAND_PREFIX, AdblockSyntax, AdblockSyntaxError, AgentCommentRuleParser, AgentParser, AppListParser, COMMA_DOMAIN_LIST_SEPARATOR, CommentMarker, CommentRuleParser, CommentRuleType, ConfigCommentRuleParser, CosmeticRuleParser, CosmeticRuleSeparator, CosmeticRuleSeparatorUtils, CosmeticRuleType, CssTree, CssTreeNodeType, CssTreeParserContext, DomainListParser, DomainUtils, EXT_CSS_LEGACY_ATTRIBUTES, EXT_CSS_PSEUDO_CLASSES, FORBIDDEN_CSS_FUNCTIONS, FilterListConverter, FilterListParser, HINT_MARKER, HintCommentRuleParser, HintParser, IF, INCLUDE, LogicalExpressionParser, LogicalExpressionUtils, METADATA_HEADERS, MODIFIERS_SEPARATOR, MODIFIER_ASSIGN_OPERATOR, MetadataCommentRuleParser, MethodListParser, ModifierListParser, ModifierParser, NEGATION_MARKER, NETWORK_RULE_EXCEPTION_MARKER, NETWORK_RULE_SEPARATOR, NetworkRuleParser, NotImplementedError, PIPE_MODIFIER_SEPARATOR, PREPROCESSOR_MARKER, ParameterListParser, PreProcessorCommentRuleParser, QuoteType, QuoteUtils, RawFilterListConverter, RawRuleConverter, RegExpUtils, RuleCategory, RuleConversionError, RuleConverter, RuleParser, SAFARI_CB_AFFINITY, SPECIAL_REGEX_SYMBOLS, StealthOptionListParser, UBO_SCRIPTLET_MASK, locRange, modifierValidator, shiftLoc, version };