@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.
package/dist/agtree.cjs CHANGED
@@ -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
@@ -281,7 +281,7 @@ const NEGATION_MARKER = '~';
281
281
  /**
282
282
  * The wildcard symbol — `*`.
283
283
  */
284
- const WILDCARD$1 = ASTERISK;
284
+ const WILDCARD = ASTERISK;
285
285
  /**
286
286
  * Classic domain separator.
287
287
  *
@@ -2880,7 +2880,7 @@ class ModifierParser {
2880
2880
  const modifierEnd = Math.max(StringUtils.skipWSBack(raw) + 1, modifierNameStart);
2881
2881
  // Modifier name can't be empty
2882
2882
  if (modifierNameStart === modifierEnd) {
2883
- throw new AdblockSyntaxError('Modifier name can\'t be empty', locRange(loc, 0, raw.length));
2883
+ throw new AdblockSyntaxError('Modifier name cannot be empty', locRange(loc, 0, raw.length));
2884
2884
  }
2885
2885
  let modifier;
2886
2886
  let value;
@@ -2904,7 +2904,7 @@ class ModifierParser {
2904
2904
  };
2905
2905
  // Value can't be empty
2906
2906
  if (assignmentIndex + 1 === modifierEnd) {
2907
- throw new AdblockSyntaxError('Modifier value can\'t be empty', locRange(loc, 0, raw.length));
2907
+ throw new AdblockSyntaxError('Modifier value cannot be empty', locRange(loc, 0, raw.length));
2908
2908
  }
2909
2909
  // Skip whitespace after the assignment operator
2910
2910
  const valueStart = StringUtils.skipWS(raw, assignmentIndex + MODIFIER_ASSIGN_OPERATOR.length);
@@ -3202,8 +3202,29 @@ const FORBIDDEN_CSS_FUNCTIONS = new Set([
3202
3202
  'url',
3203
3203
  ]);
3204
3204
 
3205
+ /**
3206
+ * @file Clone related utilities
3207
+ *
3208
+ * We should keep clone related functions in this file. Thus, we just provide
3209
+ * a simple interface for cloning values, we use it across the AGTree project,
3210
+ * and the implementation "under the hood" can be improved later, if needed.
3211
+ */
3212
+ /**
3213
+ * Clones an input value to avoid side effects. Use it only in justified cases,
3214
+ * because it can impact performance negatively.
3215
+ *
3216
+ * @param value Value to clone
3217
+ * @returns Cloned value
3218
+ */
3219
+ function clone(value) {
3220
+ // TODO: Replace cloneDeep with a more efficient implementation
3221
+ return cloneDeep(value);
3222
+ }
3223
+
3205
3224
  /**
3206
3225
  * @file Additional / helper functions for ECSSTree / CSSTree.
3226
+ *
3227
+ * @note There are no tests for some functions, but during the AGTree optimization we remove them anyway.
3207
3228
  */
3208
3229
  /**
3209
3230
  * Common CSSTree parsing options.
@@ -3339,10 +3360,10 @@ class CssTree {
3339
3360
  ast = CssTree.parse(selectorList, exports.CssTreeParserContext.selectorList);
3340
3361
  }
3341
3362
  else {
3342
- ast = cloneDeep(selectorList);
3363
+ ast = clone(selectorList);
3343
3364
  }
3344
3365
  const nodes = [];
3345
- // TODO: CSSTree types should be improved, as a workaround we use `any` here
3366
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
3346
3367
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
3347
3368
  ecssTree.walk(ast, (node) => {
3348
3369
  if (CssTree.isExtendedCssNode(node, pseudoClasses, attributeSelectors)) {
@@ -3371,9 +3392,9 @@ class CssTree {
3371
3392
  ast = CssTree.parse(selectorList, exports.CssTreeParserContext.selectorList);
3372
3393
  }
3373
3394
  else {
3374
- ast = cloneDeep(selectorList);
3395
+ ast = selectorList;
3375
3396
  }
3376
- // TODO: CSSTree types should be improved, as a workaround we use `any` here
3397
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
3377
3398
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
3378
3399
  return ecssTree.find(ast, (node) => CssTree.isExtendedCssNode(node, pseudoClasses, attributeSelectors)) !== null;
3379
3400
  }
@@ -3410,14 +3431,14 @@ class CssTree {
3410
3431
  ast = CssTree.parse(declarationList, exports.CssTreeParserContext.declarationList);
3411
3432
  }
3412
3433
  else {
3413
- ast = cloneDeep(declarationList);
3434
+ ast = clone(declarationList);
3414
3435
  }
3415
3436
  const nodes = [];
3416
3437
  // While walking the AST we should skip the nested functions,
3417
3438
  // for example skip url()s in cross-fade(url(), url()), since
3418
3439
  // cross-fade() itself is already a forbidden function
3419
3440
  let inForbiddenFunction = false;
3420
- // TODO: CSSTree types should be improved, as a workaround we use `any` here
3441
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
3421
3442
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
3422
3443
  ecssTree.walk(ast, {
3423
3444
  enter: (node) => {
@@ -3455,9 +3476,9 @@ class CssTree {
3455
3476
  ast = CssTree.parse(declarationList, exports.CssTreeParserContext.declarationList);
3456
3477
  }
3457
3478
  else {
3458
- ast = cloneDeep(declarationList);
3479
+ ast = clone(declarationList);
3459
3480
  }
3460
- // TODO: CSSTree types should be improved, as a workaround we use `any` here
3481
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
3461
3482
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
3462
3483
  return ecssTree.find(ast, (node) => CssTree.isForbiddenFunction(node, forbiddenFunctions)) !== null;
3463
3484
  }
@@ -3689,6 +3710,180 @@ class CssTree {
3689
3710
  });
3690
3711
  return result.trim();
3691
3712
  }
3713
+ /**
3714
+ * Generates string representation of the selector list.
3715
+ *
3716
+ * @param ast SelectorList AST
3717
+ * @returns String representation of the selector list
3718
+ */
3719
+ static generateSelectorListPlain(ast) {
3720
+ const result = [];
3721
+ if (!ast.children || ast.children.length === 0) {
3722
+ throw new Error('Selector list cannot be empty');
3723
+ }
3724
+ ast.children.forEach((selector, index, nodeList) => {
3725
+ if (selector.type !== exports.CssTreeNodeType.Selector) {
3726
+ throw new Error(`Unexpected node type: ${selector.type}`);
3727
+ }
3728
+ result.push(this.generateSelectorPlain(selector));
3729
+ // If there is a next node, add a comma and a space after the selector
3730
+ if (nodeList[index + 1]) {
3731
+ result.push(COMMA, SPACE);
3732
+ }
3733
+ });
3734
+ return result.join(EMPTY);
3735
+ }
3736
+ /**
3737
+ * Selector generation based on CSSTree's AST. This is necessary because CSSTree
3738
+ * only adds spaces in some edge cases.
3739
+ *
3740
+ * @param ast CSS Tree AST
3741
+ * @returns CSS selector as string
3742
+ */
3743
+ static generateSelectorPlain(ast) {
3744
+ let result = EMPTY;
3745
+ let inAttributeSelector = false;
3746
+ let depth = 0;
3747
+ let selectorListDepth = -1;
3748
+ let prevNode = ast;
3749
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
3750
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3751
+ ecssTree.walk(ast, {
3752
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
3753
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3754
+ enter: (node) => {
3755
+ depth += 1;
3756
+ // Skip attribute selector / selector list children
3757
+ if (inAttributeSelector || selectorListDepth > -1) {
3758
+ return;
3759
+ }
3760
+ switch (node.type) {
3761
+ // "Trivial" nodes
3762
+ case exports.CssTreeNodeType.TypeSelector:
3763
+ result += node.name;
3764
+ break;
3765
+ case exports.CssTreeNodeType.ClassSelector:
3766
+ result += DOT;
3767
+ result += node.name;
3768
+ break;
3769
+ case exports.CssTreeNodeType.IdSelector:
3770
+ result += HASHMARK;
3771
+ result += node.name;
3772
+ break;
3773
+ case exports.CssTreeNodeType.Identifier:
3774
+ result += node.name;
3775
+ break;
3776
+ case exports.CssTreeNodeType.Raw:
3777
+ result += node.value;
3778
+ break;
3779
+ // "Advanced" nodes
3780
+ case exports.CssTreeNodeType.Nth:
3781
+ // Default generation enough
3782
+ result += ecssTree.generate(node);
3783
+ break;
3784
+ // For example :not([id], [name])
3785
+ case exports.CssTreeNodeType.SelectorList:
3786
+ // eslint-disable-next-line no-case-declarations
3787
+ const selectors = [];
3788
+ node.children.forEach((selector) => {
3789
+ if (selector.type === exports.CssTreeNodeType.Selector) {
3790
+ selectors.push(CssTree.generateSelectorPlain(selector));
3791
+ }
3792
+ else if (selector.type === exports.CssTreeNodeType.Raw) {
3793
+ selectors.push(selector.value);
3794
+ }
3795
+ });
3796
+ // Join selector lists
3797
+ result += selectors.join(COMMA + SPACE);
3798
+ // Skip nodes here
3799
+ selectorListDepth = depth;
3800
+ break;
3801
+ case exports.CssTreeNodeType.Combinator:
3802
+ if (node.name === SPACE) {
3803
+ result += node.name;
3804
+ break;
3805
+ }
3806
+ // Prevent this case (unnecessary space): has( > .something)
3807
+ if (prevNode.type !== exports.CssTreeNodeType.Selector) {
3808
+ result += SPACE;
3809
+ }
3810
+ result += node.name;
3811
+ result += SPACE;
3812
+ break;
3813
+ case exports.CssTreeNodeType.AttributeSelector:
3814
+ result += OPEN_SQUARE_BRACKET;
3815
+ // Identifier name
3816
+ if (node.name) {
3817
+ result += node.name.name;
3818
+ }
3819
+ // Matcher operator, eg =
3820
+ if (node.matcher) {
3821
+ result += node.matcher;
3822
+ // Value can be String, Identifier or null
3823
+ if (node.value !== null) {
3824
+ // String node
3825
+ if (node.value.type === exports.CssTreeNodeType.String) {
3826
+ result += ecssTree.generate(node.value);
3827
+ }
3828
+ else if (node.value.type === exports.CssTreeNodeType.Identifier) {
3829
+ // Identifier node
3830
+ result += node.value.name;
3831
+ }
3832
+ }
3833
+ }
3834
+ // Flags
3835
+ if (node.flags) {
3836
+ // Space before flags
3837
+ result += SPACE;
3838
+ result += node.flags;
3839
+ }
3840
+ result += CLOSE_SQUARE_BRACKET;
3841
+ inAttributeSelector = true;
3842
+ break;
3843
+ case exports.CssTreeNodeType.PseudoElementSelector:
3844
+ result += COLON;
3845
+ result += COLON;
3846
+ result += node.name;
3847
+ if (node.children !== null) {
3848
+ result += OPEN_PARENTHESIS;
3849
+ }
3850
+ break;
3851
+ case exports.CssTreeNodeType.PseudoClassSelector:
3852
+ result += COLON;
3853
+ result += node.name;
3854
+ if (node.children !== null) {
3855
+ result += OPEN_PARENTHESIS;
3856
+ }
3857
+ break;
3858
+ }
3859
+ prevNode = node;
3860
+ },
3861
+ leave: (node) => {
3862
+ depth -= 1;
3863
+ if (node.type === exports.CssTreeNodeType.SelectorList && depth + 1 === selectorListDepth) {
3864
+ selectorListDepth = -1;
3865
+ }
3866
+ if (selectorListDepth > -1) {
3867
+ return;
3868
+ }
3869
+ if (node.type === exports.CssTreeNodeType.AttributeSelector) {
3870
+ inAttributeSelector = false;
3871
+ }
3872
+ if (inAttributeSelector) {
3873
+ return;
3874
+ }
3875
+ switch (node.type) {
3876
+ case exports.CssTreeNodeType.PseudoElementSelector:
3877
+ case exports.CssTreeNodeType.PseudoClassSelector:
3878
+ if (node.children) {
3879
+ result += CLOSE_PARENTHESIS;
3880
+ }
3881
+ break;
3882
+ }
3883
+ },
3884
+ });
3885
+ return result.trim();
3886
+ }
3692
3887
  /**
3693
3888
  * Block generation based on CSSTree's AST. This is necessary because CSSTree only adds spaces in some edge cases.
3694
3889
  *
@@ -3872,6 +4067,29 @@ class CssTree {
3872
4067
  });
3873
4068
  return result;
3874
4069
  }
4070
+ /**
4071
+ * Helper function to generate a raw string from a function selector's children
4072
+ *
4073
+ * @param node Function node
4074
+ * @returns Generated function value
4075
+ * @example `responseheader(name)` -> `name`
4076
+ */
4077
+ static generateFunctionPlainValue(node) {
4078
+ const result = [];
4079
+ node.children?.forEach((child) => {
4080
+ switch (child.type) {
4081
+ case exports.CssTreeNodeType.Raw:
4082
+ result.push(child.value);
4083
+ break;
4084
+ default:
4085
+ // Fallback to CSSTree's default generate function
4086
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
4087
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4088
+ result.push(ecssTree.generate(child));
4089
+ }
4090
+ });
4091
+ return result.join(EMPTY);
4092
+ }
3875
4093
  }
3876
4094
 
3877
4095
  /**
@@ -3919,7 +4137,7 @@ class ElementHidingBodyParser {
3919
4137
  * @throws If the AST is invalid
3920
4138
  */
3921
4139
  static generate(ast) {
3922
- return CssTree.generateSelectorList(ecssTree.fromPlainObject(ast.selectorList));
4140
+ return CssTree.generateSelectorListPlain(ast.selectorList);
3923
4141
  }
3924
4142
  }
3925
4143
 
@@ -4163,7 +4381,7 @@ class CssInjectionBodyParser {
4163
4381
  if (mediaQueryList || declarationList || remove) {
4164
4382
  throw new AdblockSyntaxError(
4165
4383
  // eslint-disable-next-line max-len
4166
- 'Invalid selector, regular selector elements can\'t be used after special pseudo-classes', {
4384
+ 'Invalid selector, regular selector elements cannot be used after special pseudo-classes', {
4167
4385
  start: node.loc?.start ?? loc,
4168
4386
  end: shiftLoc(loc, raw.length),
4169
4387
  });
@@ -4852,7 +5070,7 @@ function createModifierListNode(modifiers = []) {
4852
5070
  const result = {
4853
5071
  type: 'ModifierList',
4854
5072
  // We need to clone the modifiers to avoid side effects
4855
- children: cloneDeep(modifiers),
5073
+ children: modifiers.length ? clone(modifiers) : [],
4856
5074
  };
4857
5075
  return result;
4858
5076
  }
@@ -4892,8 +5110,9 @@ function hasUboModifierIndicator(rawSelectorList) {
4892
5110
  * @returns Linked list based selector
4893
5111
  */
4894
5112
  function convertSelectorToLinkedList(selector) {
5113
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
4895
5114
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4896
- return ecssTree.fromPlainObject(cloneDeep(selector));
5115
+ return ecssTree.fromPlainObject(clone(selector));
4897
5116
  }
4898
5117
  /**
4899
5118
  * Helper function that always returns the linked list version of the
@@ -4903,8 +5122,9 @@ function convertSelectorToLinkedList(selector) {
4903
5122
  * @returns Linked list based selector list
4904
5123
  */
4905
5124
  function convertSelectorListToLinkedList(selectorList) {
5125
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
4906
5126
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4907
- return ecssTree.fromPlainObject(cloneDeep(selectorList));
5127
+ return ecssTree.fromPlainObject(clone(selectorList));
4908
5128
  }
4909
5129
  /**
4910
5130
  * Helper function for checking and removing bounding combinators
@@ -5989,7 +6209,8 @@ class FilterListParser {
5989
6209
  */
5990
6210
  static generate(ast, preferRaw = false) {
5991
6211
  let result = EMPTY;
5992
- for (const rule of ast.children) {
6212
+ for (let i = 0; i < ast.children.length; i += 1) {
6213
+ const rule = ast.children[i];
5993
6214
  if (preferRaw && rule.raws?.text) {
5994
6215
  result += rule.raws.text;
5995
6216
  }
@@ -6006,6 +6227,11 @@ class FilterListParser {
6006
6227
  case 'lf':
6007
6228
  result += LF;
6008
6229
  break;
6230
+ default:
6231
+ if (i !== ast.children.length - 1) {
6232
+ result += LF;
6233
+ }
6234
+ break;
6009
6235
  }
6010
6236
  }
6011
6237
  return result;
@@ -6055,7 +6281,7 @@ class RuleConversionError extends Error {
6055
6281
  }
6056
6282
  }
6057
6283
 
6058
- var data$T = { adg_os_any:{ name:"all",
6284
+ var data$U = { adg_os_any:{ name:"all",
6059
6285
  description:"$all modifier is made of $document, $popup, and all content-type modifiers combined.",
6060
6286
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#all-modifier",
6061
6287
  negatable:false,
@@ -6081,14 +6307,14 @@ var data$T = { adg_os_any:{ name:"all",
6081
6307
  negatable:false,
6082
6308
  block_only:false } };
6083
6309
 
6084
- var data$S = { adg_os_any:{ name:"app",
6310
+ var data$T = { adg_os_any:{ name:"app",
6085
6311
  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.",
6086
6312
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#app-modifier",
6087
6313
  assignable:true,
6088
6314
  negatable:false,
6089
6315
  value_format:"pipe_separated_apps" } };
6090
6316
 
6091
- var data$R = { adg_os_any:{ name:"badfilter",
6317
+ var data$S = { adg_os_any:{ name:"badfilter",
6092
6318
  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).",
6093
6319
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#badfilter-modifier",
6094
6320
  negatable:false },
@@ -6109,19 +6335,19 @@ var data$R = { adg_os_any:{ name:"badfilter",
6109
6335
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#badfilter",
6110
6336
  negatable:false } };
6111
6337
 
6112
- var data$Q = { ubo_ext_any:{ name:"cname",
6338
+ var data$R = { ubo_ext_any:{ name:"cname",
6113
6339
  description:"When used in an exception filter,\nit will bypass blocking CNAME uncloaked requests for the current (specified) document.",
6114
6340
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#cname",
6115
6341
  negatable:false,
6116
6342
  exception_only:true } };
6117
6343
 
6118
- var data$P = { adg_os_any:{ name:"content",
6344
+ var data$Q = { adg_os_any:{ name:"content",
6119
6345
  description:"Disables HTML filtering and `$replace` rules on the pages that match the rule.",
6120
6346
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#content-modifier",
6121
6347
  negatable:false,
6122
6348
  exception_only:true } };
6123
6349
 
6124
- var data$O = { adg_os_any:{ name:"cookie",
6350
+ var data$P = { adg_os_any:{ name:"cookie",
6125
6351
  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.",
6126
6352
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#cookie-modifier",
6127
6353
  assignable:true,
@@ -6136,7 +6362,7 @@ var data$O = { adg_os_any:{ name:"cookie",
6136
6362
  value_optional:true,
6137
6363
  value_format:"^([^;=\\s]*?)((?:;(maxAge=\\d+;?)?|(sameSite=(lax|none|strict);?)?){1,3})(?<!;)$" } };
6138
6364
 
6139
- var data$N = { adg_os_any:{ name:"csp",
6365
+ var data$O = { adg_os_any:{ name:"csp",
6140
6366
  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.",
6141
6367
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#csp-modifier",
6142
6368
  conflicts:[ "domain",
@@ -6183,7 +6409,7 @@ var data$N = { adg_os_any:{ name:"csp",
6183
6409
  value_optional:true,
6184
6410
  value_format:"csp_value" } };
6185
6411
 
6186
- var data$M = { adg_os_any:{ name:"denyallow",
6412
+ var data$N = { adg_os_any:{ name:"denyallow",
6187
6413
  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.",
6188
6414
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#denyallow-modifier",
6189
6415
  conflicts:[ "to" ],
@@ -6219,7 +6445,7 @@ var data$M = { adg_os_any:{ name:"denyallow",
6219
6445
  negatable:false,
6220
6446
  value_format:"pipe_separated_denyallow_domains" } };
6221
6447
 
6222
- var data$L = { adg_os_any:{ name:"document",
6448
+ var data$M = { adg_os_any:{ name:"document",
6223
6449
  description:"The rule corresponds to the main frame document requests,\ni.e. HTML documents that are loaded in the browser tab.",
6224
6450
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#document-modifier",
6225
6451
  negatable:false },
@@ -6245,7 +6471,7 @@ var data$L = { adg_os_any:{ name:"document",
6245
6471
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#document",
6246
6472
  negatable:false } };
6247
6473
 
6248
- var data$K = { adg_any:{ name:"domain",
6474
+ var data$L = { adg_any:{ name:"domain",
6249
6475
  aliases:[ "from" ],
6250
6476
  description:"The `$domain` modifier limits the rule application area to a list of domains and their subdomains.",
6251
6477
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#domain-modifier",
@@ -6266,7 +6492,7 @@ var data$K = { adg_any:{ name:"domain",
6266
6492
  negatable:false,
6267
6493
  value_format:"pipe_separated_domains" } };
6268
6494
 
6269
- var data$J = { adg_any:{ name:"elemhide",
6495
+ var data$K = { adg_any:{ name:"elemhide",
6270
6496
  aliases:[ "ehide" ],
6271
6497
  description:"Disables any cosmetic rules on the pages matching the rule.",
6272
6498
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#elemhide-modifier",
@@ -6285,7 +6511,7 @@ var data$J = { adg_any:{ name:"elemhide",
6285
6511
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#elemhide-1",
6286
6512
  description:"Disables any cosmetic rules on the pages matching the rule." } };
6287
6513
 
6288
- var data$I = { adg_os_any:{ name:"empty",
6514
+ var data$J = { adg_os_any:{ name:"empty",
6289
6515
  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.",
6290
6516
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#empty-modifier",
6291
6517
  deprecated:true,
@@ -6302,7 +6528,7 @@ var data$I = { adg_os_any:{ name:"empty",
6302
6528
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#empty",
6303
6529
  negatable:false } };
6304
6530
 
6305
- var data$H = { adg_any:{ name:"first-party",
6531
+ var data$I = { adg_any:{ name:"first-party",
6306
6532
  aliases:[ "1p",
6307
6533
  "~third-party" ],
6308
6534
  description:"A restriction of first-party requests. Equal to `~third-party`.",
@@ -6315,7 +6541,7 @@ var data$H = { adg_any:{ name:"first-party",
6315
6541
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#1p",
6316
6542
  negatable:false } };
6317
6543
 
6318
- var data$G = { adg_os_any:{ name:"extension",
6544
+ var data$H = { adg_os_any:{ name:"extension",
6319
6545
  description:"Disables all userscripts on the pages matching this rule.",
6320
6546
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#extension-modifier",
6321
6547
  conflicts:[ "domain",
@@ -6331,7 +6557,7 @@ var data$G = { adg_os_any:{ name:"extension",
6331
6557
  inverse_conflicts:true,
6332
6558
  exception_only:true } };
6333
6559
 
6334
- var data$F = { adg_any:{ name:"font",
6560
+ var data$G = { adg_any:{ name:"font",
6335
6561
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#font-modifier",
6336
6562
  description:"The rule corresponds to requests for fonts, e.g. `.woff` filename extension." },
6337
6563
  abp_any:{ name:"font",
@@ -6341,7 +6567,7 @@ var data$F = { adg_any:{ name:"font",
6341
6567
  docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293#options",
6342
6568
  description:"The rule corresponds to requests for fonts, e.g. `.woff` filename extension." } };
6343
6569
 
6344
- var data$E = { adg_os_any:{ name:"genericblock",
6570
+ var data$F = { adg_os_any:{ name:"genericblock",
6345
6571
  description:"Disables generic basic rules on pages that correspond to exception rule.",
6346
6572
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#genericblock-modifier",
6347
6573
  conflicts:[ "domain",
@@ -6397,7 +6623,7 @@ var data$E = { adg_os_any:{ name:"genericblock",
6397
6623
  negatable:false,
6398
6624
  exception_only:true } };
6399
6625
 
6400
- var data$D = { adg_any:{ name:"generichide",
6626
+ var data$E = { adg_any:{ name:"generichide",
6401
6627
  aliases:[ "ghide" ],
6402
6628
  description:"Disables all generic cosmetic rules.",
6403
6629
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#generichide-modifier",
@@ -6429,7 +6655,7 @@ var data$D = { adg_any:{ name:"generichide",
6429
6655
  negatable:false,
6430
6656
  exception_only:true } };
6431
6657
 
6432
- var data$C = { adg_os_any:{ name:"header",
6658
+ var data$D = { adg_os_any:{ name:"header",
6433
6659
  description:"The `$header` modifier allows matching the HTTP response\nhaving a specific header with (optionally) a specific value.",
6434
6660
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#header-modifier",
6435
6661
  assignable:true,
@@ -6445,7 +6671,7 @@ var data$C = { adg_os_any:{ name:"header",
6445
6671
  assignable:true,
6446
6672
  value_format:"(?xi)\n ^\n # header name\n [\\w-]+\n (\n :\n # header value: string or regexp\n (\\w+|\\/.+\\/)\n )?" } };
6447
6673
 
6448
- var data$B = { adg_os_any:{ name:"hls",
6674
+ var data$C = { adg_os_any:{ name:"hls",
6449
6675
  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).",
6450
6676
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#hls-modifier",
6451
6677
  version_added:"CoreLibs 1.10",
@@ -6461,7 +6687,7 @@ var data$B = { adg_os_any:{ name:"hls",
6461
6687
  value_optional:true,
6462
6688
  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 )" } };
6463
6689
 
6464
- var data$A = { adg_any:{ name:"image",
6690
+ var data$B = { adg_any:{ name:"image",
6465
6691
  description:"The rule corresponds to images requests.",
6466
6692
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#image-modifier" },
6467
6693
  abp_any:{ name:"image",
@@ -6471,7 +6697,7 @@ var data$A = { adg_any:{ name:"image",
6471
6697
  description:"The rule corresponds to images requests.",
6472
6698
  docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293#options" } };
6473
6699
 
6474
- var data$z = { adg_any:{ name:"important",
6700
+ var data$A = { adg_any:{ name:"important",
6475
6701
  description:"The `$important` modifier applied to a rule increases its priority\nover any other rule without `$important` modifier. Even over basic exception rules.",
6476
6702
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#important-modifier",
6477
6703
  negatable:false },
@@ -6480,7 +6706,7 @@ var data$z = { adg_any:{ name:"important",
6480
6706
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#important",
6481
6707
  negatable:false } };
6482
6708
 
6483
- var data$y = { adg_os_any:{ name:"inline-font",
6709
+ var data$z = { adg_os_any:{ name:"inline-font",
6484
6710
  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```",
6485
6711
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#inline-font-modifier" },
6486
6712
  adg_ext_any:{ name:"inline-font",
@@ -6490,7 +6716,7 @@ var data$y = { adg_os_any:{ name:"inline-font",
6490
6716
  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```",
6491
6717
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#inline-font" } };
6492
6718
 
6493
- var data$x = { adg_os_any:{ name:"inline-script",
6719
+ var data$y = { adg_os_any:{ name:"inline-script",
6494
6720
  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```",
6495
6721
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#inline-script-modifier" },
6496
6722
  adg_ext_any:{ name:"inline-script",
@@ -6500,7 +6726,7 @@ var data$x = { adg_os_any:{ name:"inline-script",
6500
6726
  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```",
6501
6727
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#inline-script" } };
6502
6728
 
6503
- var data$w = { adg_os_any:{ name:"jsinject",
6729
+ var data$x = { adg_os_any:{ name:"jsinject",
6504
6730
  description:"Forbids adding of javascript code to the page.",
6505
6731
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#jsinject-modifier",
6506
6732
  conflicts:[ "domain",
@@ -6559,7 +6785,7 @@ var data$w = { adg_os_any:{ name:"jsinject",
6559
6785
  negatable:false,
6560
6786
  exception_only:true } };
6561
6787
 
6562
- var data$v = { adg_os_any:{ name:"jsonprune",
6788
+ var data$w = { adg_os_any:{ name:"jsonprune",
6563
6789
  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.",
6564
6790
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#jsonprune-modifier",
6565
6791
  assignable:true,
@@ -6567,7 +6793,7 @@ var data$v = { adg_os_any:{ name:"jsonprune",
6567
6793
  value_optional:true,
6568
6794
  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 $" } };
6569
6795
 
6570
- var data$u = { adg_any:{ name:"match-case",
6796
+ var data$v = { adg_any:{ name:"match-case",
6571
6797
  description:"This modifier defines a rule which applies only to addresses that match the case.\nDefault rules are case-insensitive.",
6572
6798
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#match-case-modifier" },
6573
6799
  abp_any:{ name:"match-case",
@@ -6577,7 +6803,7 @@ var data$u = { adg_any:{ name:"match-case",
6577
6803
  description:"This modifier defines a rule which applies only to addresses that match the case.\nDefault rules are case-insensitive.",
6578
6804
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#match-case" } };
6579
6805
 
6580
- var data$t = { adg_any:{ name:"media",
6806
+ var data$u = { adg_any:{ name:"media",
6581
6807
  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.",
6582
6808
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#media-modifier" },
6583
6809
  abp_any:{ name:"media",
@@ -6587,7 +6813,7 @@ var data$t = { adg_any:{ name:"media",
6587
6813
  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.",
6588
6814
  docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293#options" } };
6589
6815
 
6590
- var data$s = { adg_os_any:{ name:"method",
6816
+ var data$t = { adg_os_any:{ name:"method",
6591
6817
  description:"This modifier limits the rule scope to requests that use the specified set of HTTP methods.\nNegated methods are allowed.",
6592
6818
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#method-modifier",
6593
6819
  negatable:false,
@@ -6606,7 +6832,7 @@ var data$s = { adg_os_any:{ name:"method",
6606
6832
  assignable:true,
6607
6833
  value_format:"pipe_separated_methods" } };
6608
6834
 
6609
- var data$r = { adg_os_any:{ name:"mp4",
6835
+ var data$s = { adg_os_any:{ name:"mp4",
6610
6836
  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.",
6611
6837
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#mp4-modifier",
6612
6838
  deprecated:true,
@@ -6623,7 +6849,7 @@ var data$r = { adg_os_any:{ name:"mp4",
6623
6849
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#mp4",
6624
6850
  negatable:false } };
6625
6851
 
6626
- var data$q = { adg_os_any:{ name:"network",
6852
+ var data$r = { adg_os_any:{ name:"network",
6627
6853
  description:"This is basically a Firewall-kind of rules allowing to fully block\nor unblock access to a specified remote address.",
6628
6854
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#network-modifier",
6629
6855
  conflicts:[ "app",
@@ -6631,7 +6857,7 @@ var data$q = { adg_os_any:{ name:"network",
6631
6857
  inverse_conflicts:true,
6632
6858
  negatable:false } };
6633
6859
 
6634
- var data$p = { adg_os_any:{ name:"_",
6860
+ var data$q = { adg_os_any:{ name:"_",
6635
6861
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#noop-modifier",
6636
6862
  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.",
6637
6863
  negatable:false },
@@ -6652,13 +6878,13 @@ var data$p = { adg_os_any:{ name:"_",
6652
6878
  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.",
6653
6879
  negatable:false } };
6654
6880
 
6655
- var data$o = { adg_any:{ name:"object-subrequest",
6881
+ var data$p = { adg_any:{ name:"object-subrequest",
6656
6882
  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).",
6657
6883
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#object-subrequest-modifier",
6658
6884
  removed:true,
6659
6885
  removal_message:"The `$object-subrequest` modifier is removed and is no longer supported.\nRules with it are considered as invalid." } };
6660
6886
 
6661
- var data$n = { adg_any:{ name:"object",
6887
+ var data$o = { adg_any:{ name:"object",
6662
6888
  description:"The rule corresponds to browser plugins resources, e.g. Java or Flash",
6663
6889
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#object-modifier" },
6664
6890
  abp_any:{ name:"object",
@@ -6668,7 +6894,7 @@ var data$n = { adg_any:{ name:"object",
6668
6894
  description:"The rule corresponds to browser plugins resources, e.g. Java or Flash.",
6669
6895
  docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" } };
6670
6896
 
6671
- var data$m = { adg_any:{ name:"other",
6897
+ var data$n = { adg_any:{ name:"other",
6672
6898
  description:"The rule applies to requests for which the type has not been determined\nor does not match the types listed above.",
6673
6899
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#other-modifier" },
6674
6900
  abp_any:{ name:"other",
@@ -6678,7 +6904,7 @@ var data$m = { adg_any:{ name:"other",
6678
6904
  description:"The rule applies to requests for which the type has not been determined\nor does not match the types listed above.",
6679
6905
  docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" } };
6680
6906
 
6681
- var data$l = { adg_os_any:{ name:"permissions",
6907
+ var data$m = { adg_os_any:{ name:"permissions",
6682
6908
  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.",
6683
6909
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#permissions-modifier",
6684
6910
  version_added:"CoreLibs 1.11",
@@ -6691,7 +6917,7 @@ var data$l = { adg_os_any:{ name:"permissions",
6691
6917
  value_optional:true,
6692
6918
  value_format:"permissions_value" } };
6693
6919
 
6694
- var data$k = { adg_any:{ name:"ping",
6920
+ var data$l = { adg_any:{ name:"ping",
6695
6921
  description:"The rule corresponds to requests caused by either navigator.sendBeacon() or the ping attribute on links.",
6696
6922
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#ping-modifier" },
6697
6923
  abp_any:{ name:"ping",
@@ -6701,13 +6927,13 @@ var data$k = { adg_any:{ name:"ping",
6701
6927
  description:"The rule corresponds to requests caused by either navigator.sendBeacon() or the ping attribute on links.",
6702
6928
  docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options" } };
6703
6929
 
6704
- var data$j = { ubo_ext_any:{ name:"popunder",
6930
+ var data$k = { ubo_ext_any:{ name:"popunder",
6705
6931
  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.",
6706
6932
  docs:"https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#popunder",
6707
6933
  negatable:false,
6708
6934
  block_only:true } };
6709
6935
 
6710
- var data$i = { adg_any:{ name:"popup",
6936
+ var data$j = { adg_any:{ name:"popup",
6711
6937
  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.",
6712
6938
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#popup-modifier",
6713
6939
  negatable:false },
@@ -6720,7 +6946,7 @@ var data$i = { adg_any:{ name:"popup",
6720
6946
  docs:"https://help.adblockplus.org/hc/en-us/articles/360062733293-How-to-write-filters#type-options",
6721
6947
  negatable:false } };
6722
6948
 
6723
- var data$h = { adg_os_any:{ name:"redirect-rule",
6949
+ var data$i = { adg_os_any:{ name:"redirect-rule",
6724
6950
  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.",
6725
6951
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#redirect-rule-modifier",
6726
6952
  conflicts:[ "domain",
@@ -6811,7 +7037,7 @@ var data$h = { adg_os_any:{ name:"redirect-rule",
6811
7037
  negatable:false,
6812
7038
  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]+)?$" } };
6813
7039
 
6814
- var data$g = { adg_os_any:{ name:"redirect",
7040
+ var data$h = { adg_os_any:{ name:"redirect",
6815
7041
  description:"Used to redirect web requests to a local \"resource\".",
6816
7042
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#redirect-modifier",
6817
7043
  assignable:true,
@@ -6839,6 +7065,17 @@ var data$g = { adg_os_any:{ name:"redirect",
6839
7065
  negatable:false,
6840
7066
  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 )$" } };
6841
7067
 
7068
+ var data$g = { adg_os_any:{ name:"referrerpolicy",
7069
+ description:"This modifier allows overriding of a page's referrer policy.",
7070
+ docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#referrerpolicy-modifier",
7071
+ conflicts:[ "document",
7072
+ "subdocument" ],
7073
+ inverse_conflicts:true,
7074
+ assignable:true,
7075
+ negatable:false,
7076
+ value_optional:true,
7077
+ value_format:"referrerpolicy_value" } };
7078
+
6842
7079
  var data$f = { adg_os_any:{ name:"removeheader",
6843
7080
  description:"Rules with the `$removeheader` modifier are intended to remove headers from HTTP requests and responses.",
6844
7081
  docs:"https://adguard.app/kb/general/ad-filtering/create-own-filters/#removeheader-modifier",
@@ -7148,46 +7385,47 @@ var data = { adg_any:{ name:"xmlhttprequest",
7148
7385
  // @ts-nocheck
7149
7386
  // Please keep imports and exports in alphabetical order
7150
7387
  const rawModifiersData = {
7151
- all: data$T,
7152
- app: data$S,
7153
- badfilter: data$R,
7154
- cname: data$Q,
7155
- content: data$P,
7156
- cookie: data$O,
7157
- csp: data$N,
7158
- denyallow: data$M,
7159
- document: data$L,
7160
- domain: data$K,
7161
- elemhide: data$J,
7162
- empty: data$I,
7163
- firstParty: data$H,
7164
- extension: data$G,
7165
- font: data$F,
7166
- genericblock: data$E,
7167
- generichide: data$D,
7168
- header: data$C,
7169
- hls: data$B,
7170
- image: data$A,
7171
- important: data$z,
7172
- inlineFont: data$y,
7173
- inlineScript: data$x,
7174
- jsinject: data$w,
7175
- jsonprune: data$v,
7176
- matchCase: data$u,
7177
- media: data$t,
7178
- method: data$s,
7179
- mp4: data$r,
7180
- network: data$q,
7181
- noop: data$p,
7182
- objectSubrequest: data$o,
7183
- object: data$n,
7184
- other: data$m,
7185
- permissions: data$l,
7186
- ping: data$k,
7187
- popunder: data$j,
7188
- popup: data$i,
7189
- redirectRule: data$h,
7190
- redirect: data$g,
7388
+ all: data$U,
7389
+ app: data$T,
7390
+ badfilter: data$S,
7391
+ cname: data$R,
7392
+ content: data$Q,
7393
+ cookie: data$P,
7394
+ csp: data$O,
7395
+ denyallow: data$N,
7396
+ document: data$M,
7397
+ domain: data$L,
7398
+ elemhide: data$K,
7399
+ empty: data$J,
7400
+ firstParty: data$I,
7401
+ extension: data$H,
7402
+ font: data$G,
7403
+ genericblock: data$F,
7404
+ generichide: data$E,
7405
+ header: data$D,
7406
+ hls: data$C,
7407
+ image: data$B,
7408
+ important: data$A,
7409
+ inlineFont: data$z,
7410
+ inlineScript: data$y,
7411
+ jsinject: data$x,
7412
+ jsonprune: data$w,
7413
+ matchCase: data$v,
7414
+ media: data$u,
7415
+ method: data$t,
7416
+ mp4: data$s,
7417
+ network: data$r,
7418
+ noop: data$q,
7419
+ objectSubrequest: data$p,
7420
+ object: data$o,
7421
+ other: data$n,
7422
+ permissions: data$m,
7423
+ ping: data$l,
7424
+ popunder: data$k,
7425
+ popup: data$j,
7426
+ redirectRule: data$i,
7427
+ redirect: data$h,
7428
+ referrerpolicy: data$g,
7191
7429
  removeheader: data$f,
7192
7430
  removeparam: data$e,
7193
7431
  replace: data$d,
@@ -7380,7 +7618,7 @@ const ALLOWED_CSP_DIRECTIVES = new Set([
7380
7618
  'worker-src',
7381
7619
  ]);
7382
7620
  /**
7383
- * Allowed stealth options for $permissions modifier.
7621
+ * Allowed directives for $permissions modifier.
7384
7622
  *
7385
7623
  * @see {@link https://adguard.app/kb/general/ad-filtering/create-own-filters/#permissions-modifier}
7386
7624
  */
@@ -7430,6 +7668,21 @@ const PERMISSIONS_TOKEN_SELF = 'self';
7430
7668
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Permissions_Policy#allowlists}
7431
7669
  */
7432
7670
  const EMPTY_PERMISSIONS_ALLOWLIST = `${OPEN_PARENTHESIS}${CLOSE_PARENTHESIS}`;
7671
+ /**
7672
+ * Allowed directives for $referrerpolicy modifier.
7673
+ *
7674
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy}
7675
+ */
7676
+ const REFERRER_POLICY_DIRECTIVES = new Set([
7677
+ 'no-referrer',
7678
+ 'no-referrer-when-downgrade',
7679
+ 'origin',
7680
+ 'origin-when-cross-origin',
7681
+ 'same-origin',
7682
+ 'strict-origin',
7683
+ 'strict-origin-when-cross-origin',
7684
+ 'unsafe-url',
7685
+ ]);
7433
7686
  /**
7434
7687
  * Prefixes for error messages used in modifier validation.
7435
7688
  */
@@ -7442,6 +7695,7 @@ const VALIDATION_ERROR_PREFIX = {
7442
7695
  INVALID_PERMISSION_DIRECTIVE: 'Invalid Permissions-Policy directive for the modifier',
7443
7696
  INVALID_PERMISSION_ORIGINS: 'Origins in the value is invalid for the modifier and the directive',
7444
7697
  INVALID_PERMISSION_ORIGIN_QUOTES: 'Double quotes should be used for origins in the value of the modifier',
7698
+ INVALID_REFERRER_POLICY_DIRECTIVE: 'Invalid Referrer-Policy directive for the modifier',
7445
7699
  MIXED_NEGATIONS: 'Simultaneous usage of negated and not negated values is forbidden for the modifier',
7446
7700
  NO_CSP_VALUE: 'No CSP value for the modifier and the directive',
7447
7701
  NO_CSP_DIRECTIVE_QUOTE: 'CSP directives should no be quoted for the modifier',
@@ -7560,14 +7814,14 @@ const getSpecificBlockerData = (modifiersData, blockerPrefix, modifierName) => {
7560
7814
  * @example
7561
7815
  * `example.*` — matches with any TLD, e.g. `example.org`, `example.com`, etc.
7562
7816
  */
7563
- const WILDCARD_TLD = DOT + WILDCARD$1;
7817
+ const WILDCARD_TLD = DOT + WILDCARD;
7564
7818
  /**
7565
7819
  * Marker for a wildcard subdomain — `*.`.
7566
7820
  *
7567
7821
  * @example
7568
7822
  * `*.example.org` — matches with any subdomain, e.g. `foo.example.org` or `bar.example.org`
7569
7823
  */
7570
- const WILDCARD_SUBDOMAIN = WILDCARD$1 + DOT;
7824
+ const WILDCARD_SUBDOMAIN = WILDCARD + DOT;
7571
7825
  class DomainUtils {
7572
7826
  /**
7573
7827
  * Check if the input is a valid domain or hostname.
@@ -7578,7 +7832,7 @@ class DomainUtils {
7578
7832
  static isValidDomainOrHostname(domain) {
7579
7833
  let domainToCheck = domain;
7580
7834
  // Wildcard-only domain, typically a generic rule
7581
- if (domainToCheck === WILDCARD$1) {
7835
+ if (domainToCheck === WILDCARD) {
7582
7836
  return true;
7583
7837
  }
7584
7838
  // https://adguard.com/kb/general/ad-filtering/create-own-filters/#wildcard-for-tld
@@ -7765,6 +8019,7 @@ var CustomValueFormatValidatorName;
7765
8019
  CustomValueFormatValidatorName["Domain"] = "pipe_separated_domains";
7766
8020
  CustomValueFormatValidatorName["Method"] = "pipe_separated_methods";
7767
8021
  CustomValueFormatValidatorName["Permissions"] = "permissions_value";
8022
+ CustomValueFormatValidatorName["ReferrerPolicy"] = "referrerpolicy_value";
7768
8023
  CustomValueFormatValidatorName["StealthOption"] = "pipe_separated_stealth_options";
7769
8024
  })(CustomValueFormatValidatorName || (CustomValueFormatValidatorName = {}));
7770
8025
  /**
@@ -7798,7 +8053,7 @@ const isValidAppNameChunk = (chunk) => {
7798
8053
  const isValidAppModifierValue = (value) => {
7799
8054
  // $app modifier does not support wildcard tld
7800
8055
  // https://adguard.app/kb/general/ad-filtering/create-own-filters/#app-modifier
7801
- if (value.includes(WILDCARD$1)) {
8056
+ if (value.includes(WILDCARD)) {
7802
8057
  return false;
7803
8058
  }
7804
8059
  return value
@@ -7863,7 +8118,7 @@ const isValidDenyAllowModifierValue = (value) => {
7863
8118
  // $denyallow modifier does not support wildcard tld
7864
8119
  // https://adguard.app/kb/general/ad-filtering/create-own-filters/#denyallow-modifier
7865
8120
  // but here we are simply checking whether the value contains wildcard `*`, not ends with `.*`
7866
- if (value.includes(WILDCARD$1)) {
8121
+ if (value.includes(WILDCARD)) {
7867
8122
  return false;
7868
8123
  }
7869
8124
  // TODO: add cache for domains validation
@@ -8160,7 +8415,7 @@ const validatePermissionAllowlist = (allowlist, directive, modifierName) => {
8160
8415
  // `*` is one of available permissions tokens
8161
8416
  // e.g. 'fullscreen=*'
8162
8417
  // https://w3c.github.io/webappsec-permissions-policy/#structured-header-serialization
8163
- if (allowlist === WILDCARD$1
8418
+ if (allowlist === WILDCARD
8164
8419
  // e.g. 'autoplay=()'
8165
8420
  || allowlist === EMPTY_PERMISSIONS_ALLOWLIST) {
8166
8421
  return { valid: true };
@@ -8221,6 +8476,26 @@ const validatePermissions = (modifier) => {
8221
8476
  }
8222
8477
  return { valid: true };
8223
8478
  };
8479
+ /**
8480
+ * Validates `referrerpolicy_value` custom value format.
8481
+ * Used for $referrerpolicy modifier.
8482
+ *
8483
+ * @param modifier Modifier AST node.
8484
+ *
8485
+ * @returns Validation result.
8486
+ */
8487
+ const validateReferrerPolicy = (modifier) => {
8488
+ if (!modifier.value?.value) {
8489
+ return getValueRequiredValidationResult(modifier.modifier.value);
8490
+ }
8491
+ const modifierName = modifier.modifier.value;
8492
+ const modifierValue = modifier.value.value;
8493
+ if (!REFERRER_POLICY_DIRECTIVES.has(modifierValue)) {
8494
+ // eslint-disable-next-line max-len
8495
+ return getInvalidValidationResult(`${VALIDATION_ERROR_PREFIX.INVALID_REFERRER_POLICY_DIRECTIVE}: '${modifierName}': '${modifierValue}'`);
8496
+ }
8497
+ return { valid: true };
8498
+ };
8224
8499
  /**
8225
8500
  * Map of all available pre-defined validators for modifiers with custom `value_format`.
8226
8501
  */
@@ -8231,6 +8506,7 @@ const CUSTOM_VALUE_FORMAT_MAP = {
8231
8506
  [CustomValueFormatValidatorName.Domain]: validatePipeSeparatedDomains,
8232
8507
  [CustomValueFormatValidatorName.Method]: validatePipeSeparatedMethods,
8233
8508
  [CustomValueFormatValidatorName.Permissions]: validatePermissions,
8509
+ [CustomValueFormatValidatorName.ReferrerPolicy]: validateReferrerPolicy,
8234
8510
  [CustomValueFormatValidatorName.StealthOption]: validatePipeSeparatedStealthOptions,
8235
8511
  };
8236
8512
  /**
@@ -8424,7 +8700,7 @@ class ModifierValidator {
8424
8700
  * @returns Result of modifier validation.
8425
8701
  */
8426
8702
  validate = (syntax, rawModifier, isException = false) => {
8427
- const modifier = cloneDeep(rawModifier);
8703
+ const modifier = clone(rawModifier);
8428
8704
  // special case: handle noop modifier which may be used as multiple underscores (not just one)
8429
8705
  // https://adguard.com/kb/general/ad-filtering/create-own-filters/#noop-modifier
8430
8706
  if (modifier.modifier.value.startsWith(UNDERSCORE)) {
@@ -8503,7 +8779,9 @@ class ConverterBase {
8503
8779
  * Converts some data to AdGuard format
8504
8780
  *
8505
8781
  * @param data Data to convert
8506
- * @returns Converted data
8782
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
8783
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
8784
+ * If the node was not converted, the result will contain the original node with the same object reference
8507
8785
  * @throws If the data is invalid or incompatible
8508
8786
  */
8509
8787
  static convertToAdg(data) {
@@ -8513,7 +8791,9 @@ class ConverterBase {
8513
8791
  * Converts some data to Adblock Plus format
8514
8792
  *
8515
8793
  * @param data Data to convert
8516
- * @returns Converted data
8794
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
8795
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
8796
+ * If the node was not converted, the result will contain the original node with the same object reference
8517
8797
  * @throws If the data is invalid or incompatible
8518
8798
  */
8519
8799
  static convertToAbp(data) {
@@ -8523,7 +8803,9 @@ class ConverterBase {
8523
8803
  * Converts some data to uBlock Origin format
8524
8804
  *
8525
8805
  * @param data Data to convert
8526
- * @returns Converted data
8806
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
8807
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
8808
+ * If the node was not converted, the result will contain the original node with the same object reference
8527
8809
  * @throws If the data is invalid or incompatible
8528
8810
  */
8529
8811
  static convertToUbo(data) {
@@ -8547,7 +8829,9 @@ class RuleConverterBase extends ConverterBase {
8547
8829
  * Converts an adblock filtering rule to AdGuard format, if possible.
8548
8830
  *
8549
8831
  * @param rule Rule node to convert
8550
- * @returns Array of converted rule nodes
8832
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
8833
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
8834
+ * If the rule was not converted, the result array will contain the original node with the same object reference
8551
8835
  * @throws If the rule is invalid or cannot be converted
8552
8836
  */
8553
8837
  static convertToAdg(rule) {
@@ -8557,7 +8841,9 @@ class RuleConverterBase extends ConverterBase {
8557
8841
  * Converts an adblock filtering rule to Adblock Plus format, if possible.
8558
8842
  *
8559
8843
  * @param rule Rule node to convert
8560
- * @returns Array of converted rule nodes
8844
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
8845
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
8846
+ * If the rule was not converted, the result array will contain the original node with the same object reference
8561
8847
  * @throws If the rule is invalid or cannot be converted
8562
8848
  */
8563
8849
  static convertToAbp(rule) {
@@ -8567,7 +8853,9 @@ class RuleConverterBase extends ConverterBase {
8567
8853
  * Converts an adblock filtering rule to uBlock Origin format, if possible.
8568
8854
  *
8569
8855
  * @param rule Rule node to convert
8570
- * @returns Array of converted rule nodes
8856
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
8857
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
8858
+ * If the rule was not converted, the result array will contain the original node with the same object reference
8571
8859
  * @throws If the rule is invalid or cannot be converted
8572
8860
  */
8573
8861
  static convertToUbo(rule) {
@@ -8575,6 +8863,37 @@ class RuleConverterBase extends ConverterBase {
8575
8863
  }
8576
8864
  }
8577
8865
 
8866
+ /**
8867
+ * @file Conversion result interface and helper functions
8868
+ */
8869
+ /**
8870
+ * Helper function to create a generic conversion result.
8871
+ *
8872
+ * @param result Conversion result
8873
+ * @param isConverted Indicates whether the input item was converted
8874
+ * @template T Type of the item to convert
8875
+ * @template U Type of the conversion result (defaults to `T`, but can be `T[]` as well)
8876
+ * @returns Generic conversion result
8877
+ */
8878
+ // eslint-disable-next-line max-len
8879
+ function createConversionResult(result, isConverted) {
8880
+ return {
8881
+ result,
8882
+ isConverted,
8883
+ };
8884
+ }
8885
+ /**
8886
+ * Helper function to create a node conversion result.
8887
+ *
8888
+ * @param nodes Array of nodes
8889
+ * @param isConverted Indicates whether the input item was converted
8890
+ * @template T Type of the node (extends `Node`)
8891
+ * @returns Node conversion result
8892
+ */
8893
+ function createNodeConversionResult(nodes, isConverted) {
8894
+ return createConversionResult(nodes, isConverted);
8895
+ }
8896
+
8578
8897
  /**
8579
8898
  * @file Comment rule converter
8580
8899
  */
@@ -8588,27 +8907,30 @@ class CommentRuleConverter extends RuleConverterBase {
8588
8907
  * Converts a comment rule to AdGuard format, if possible.
8589
8908
  *
8590
8909
  * @param rule Rule node to convert
8591
- * @returns Array of converted rule nodes
8910
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
8911
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
8912
+ * If the rule was not converted, the result array will contain the original node with the same object reference
8592
8913
  * @throws If the rule is invalid or cannot be converted
8593
8914
  */
8594
8915
  static convertToAdg(rule) {
8595
- // Clone the provided AST node to avoid side effects
8596
- const ruleNode = cloneDeep(rule);
8597
8916
  // TODO: Add support for other comment types, if needed
8598
8917
  // Main task is # -> ! conversion
8599
- switch (ruleNode.type) {
8918
+ switch (rule.type) {
8600
8919
  case exports.CommentRuleType.CommentRule:
8601
- // 'Comment' uBO style comments
8602
- if (ruleNode.type === exports.CommentRuleType.CommentRule
8603
- && ruleNode.marker.value === exports.CommentMarker.Hashmark) {
8604
- ruleNode.marker.value = exports.CommentMarker.Regular;
8605
- // Add the hashmark to the beginning of the comment
8606
- ruleNode.text.value = `${SPACE}${exports.CommentMarker.Hashmark}${ruleNode.text.value}`;
8920
+ // Check if the rule needs to be converted
8921
+ if (rule.type === exports.CommentRuleType.CommentRule && rule.marker.value === exports.CommentMarker.Hashmark) {
8922
+ // Add a ! to the beginning of the comment
8923
+ // TODO: Replace with custom clone method
8924
+ const ruleClone = clone(rule);
8925
+ ruleClone.marker.value = exports.CommentMarker.Regular;
8926
+ // Add the hashmark to the beginning of the comment text
8927
+ ruleClone.text.value = `${SPACE}${exports.CommentMarker.Hashmark}${ruleClone.text.value}`;
8928
+ return createNodeConversionResult([ruleClone], true);
8607
8929
  }
8608
- return [ruleNode];
8930
+ return createNodeConversionResult([rule], false);
8609
8931
  // Leave any other comment rule as is
8610
8932
  default:
8611
- return [ruleNode];
8933
+ return createNodeConversionResult([rule], false);
8612
8934
  }
8613
8935
  }
8614
8936
  }
@@ -8778,6 +9100,58 @@ class RegExpUtils {
8778
9100
  }
8779
9101
  }
8780
9102
 
9103
+ /**
9104
+ * @file Custom clone functions for AST nodes, this is probably the most efficient way to clone AST nodes.
9105
+ * @todo Maybe move them to parser classes as 'clone' methods
9106
+ */
9107
+ /**
9108
+ * Clones a scriptlet rule node.
9109
+ *
9110
+ * @param node Node to clone
9111
+ * @returns Cloned node
9112
+ */
9113
+ function cloneScriptletRuleNode(node) {
9114
+ return {
9115
+ type: node.type,
9116
+ children: node.children.map((child) => ({ ...child })),
9117
+ };
9118
+ }
9119
+ /**
9120
+ * Clones a domain list node.
9121
+ *
9122
+ * @param node Node to clone
9123
+ * @returns Cloned node
9124
+ */
9125
+ function cloneDomainListNode(node) {
9126
+ return {
9127
+ type: node.type,
9128
+ separator: node.separator,
9129
+ children: node.children.map((domain) => ({ ...domain })),
9130
+ };
9131
+ }
9132
+ /**
9133
+ * Clones a modifier list node.
9134
+ *
9135
+ * @param node Node to clone
9136
+ * @returns Cloned node
9137
+ */
9138
+ function cloneModifierListNode(node) {
9139
+ return {
9140
+ type: node.type,
9141
+ children: node.children.map((modifier) => {
9142
+ const res = {
9143
+ type: modifier.type,
9144
+ exception: modifier.exception,
9145
+ modifier: { ...modifier.modifier },
9146
+ };
9147
+ if (modifier.value) {
9148
+ res.value = { ...modifier.value };
9149
+ }
9150
+ return res;
9151
+ }),
9152
+ };
9153
+ }
9154
+
8781
9155
  /**
8782
9156
  * @file HTML filtering rule converter
8783
9157
  */
@@ -8790,16 +9164,22 @@ class RegExpUtils {
8790
9164
  *
8791
9165
  * @see {@link https://adguard.com/kb/general/ad-filtering/create-own-filters/#html-filtering-rules}
8792
9166
  */
8793
- const ADGUARD_HTML_DEFAULT_MAX_LENGTH = 8192;
8794
- const ADGUARD_HTML_CONVERSION_MAX_LENGTH = ADGUARD_HTML_DEFAULT_MAX_LENGTH * 32;
9167
+ const ADG_HTML_DEFAULT_MAX_LENGTH = 8192;
9168
+ const ADG_HTML_CONVERSION_MAX_LENGTH = ADG_HTML_DEFAULT_MAX_LENGTH * 32;
8795
9169
  const NOT_SPECIFIED = -1;
8796
- const CONTAINS$1 = 'contains';
8797
- const HAS_TEXT$1 = 'has-text';
8798
- const MAX_LENGTH = 'max-length';
8799
- const MIN_LENGTH = 'min-length';
8800
- const MIN_TEXT_LENGTH = 'min-text-length';
8801
- const TAG_CONTENT = 'tag-content';
8802
- const WILDCARD = 'wildcard';
9170
+ var PseudoClasses$1;
9171
+ (function (PseudoClasses) {
9172
+ PseudoClasses["Contains"] = "contains";
9173
+ PseudoClasses["HasText"] = "has-text";
9174
+ PseudoClasses["MinTextLength"] = "min-text-length";
9175
+ })(PseudoClasses$1 || (PseudoClasses$1 = {}));
9176
+ var AttributeSelectors;
9177
+ (function (AttributeSelectors) {
9178
+ AttributeSelectors["MaxLength"] = "max-length";
9179
+ AttributeSelectors["MinLength"] = "min-length";
9180
+ AttributeSelectors["TagContent"] = "tag-content";
9181
+ AttributeSelectors["Wildcard"] = "wildcard";
9182
+ })(AttributeSelectors || (AttributeSelectors = {}));
8803
9183
  /**
8804
9184
  * HTML filtering rule converter class
8805
9185
  *
@@ -8822,16 +9202,23 @@ class HtmlRuleConverter extends RuleConverterBase {
8822
9202
  * ```
8823
9203
  *
8824
9204
  * @param rule Rule node to convert
8825
- * @returns Array of converted rule nodes
9205
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
9206
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
9207
+ * If the rule was not converted, the result array will contain the original node with the same object reference
8826
9208
  * @throws If the rule is invalid or cannot be converted
8827
9209
  */
8828
9210
  static convertToAdg(rule) {
8829
- // Clone the provided AST node to avoid side effects
8830
- const ruleNode = cloneDeep(rule);
9211
+ // Ignore AdGuard rules
9212
+ if (rule.syntax === exports.AdblockSyntax.Adg) {
9213
+ return createNodeConversionResult([rule], false);
9214
+ }
9215
+ if (rule.syntax === exports.AdblockSyntax.Abp) {
9216
+ throw new RuleConversionError('Invalid rule, ABP does not support HTML filtering rules');
9217
+ }
8831
9218
  // Prepare the conversion result
8832
9219
  const conversionResult = [];
8833
9220
  // Iterate over selector list
8834
- for (const selector of ruleNode.body.body.children) {
9221
+ for (const selector of rule.body.body.children) {
8835
9222
  // Check selector, just in case
8836
9223
  if (selector.type !== exports.CssTreeNodeType.Selector) {
8837
9224
  throw new RuleConversionError(`Expected selector, got '${selector.type}'`);
@@ -8858,24 +9245,24 @@ class HtmlRuleConverter extends RuleConverterBase {
8858
9245
  throw new RuleConversionError('Tag selector should be the first child, if present');
8859
9246
  }
8860
9247
  // Simply store the tag selector
8861
- convertedSelector.children.push(cloneDeep(node));
9248
+ convertedSelector.children.push(clone(node));
8862
9249
  break;
8863
9250
  case exports.CssTreeNodeType.AttributeSelector:
8864
9251
  // Check if the attribute selector is a special AdGuard attribute
8865
9252
  switch (node.name.name) {
8866
- case MIN_LENGTH:
9253
+ case AttributeSelectors.MinLength:
8867
9254
  minLength = CssTree.parseAttributeSelectorValueAsNumber(node);
8868
9255
  break;
8869
- case MAX_LENGTH:
9256
+ case AttributeSelectors.MaxLength:
8870
9257
  maxLength = CssTree.parseAttributeSelectorValueAsNumber(node);
8871
9258
  break;
8872
- case TAG_CONTENT:
8873
- case WILDCARD:
9259
+ case AttributeSelectors.TagContent:
9260
+ case AttributeSelectors.Wildcard:
8874
9261
  CssTree.assertAttributeSelectorHasStringValue(node);
8875
- convertedSelector.children.push(cloneDeep(node));
9262
+ convertedSelector.children.push(clone(node));
8876
9263
  break;
8877
9264
  default:
8878
- convertedSelector.children.push(cloneDeep(node));
9265
+ convertedSelector.children.push(clone(node));
8879
9266
  }
8880
9267
  break;
8881
9268
  case exports.CssTreeNodeType.PseudoClassSelector:
@@ -8889,18 +9276,18 @@ class HtmlRuleConverter extends RuleConverterBase {
8889
9276
  }
8890
9277
  // Process the pseudo class based on its name
8891
9278
  switch (node.name) {
8892
- case HAS_TEXT$1:
8893
- case CONTAINS$1:
9279
+ case PseudoClasses$1.HasText:
9280
+ case PseudoClasses$1.Contains:
8894
9281
  // Check if the argument is a RegExp
8895
9282
  if (RegExpUtils.isRegexPattern(arg.value)) {
8896
9283
  // TODO: Add some support for RegExp patterns later
8897
9284
  // Need to find a way to convert some RegExp patterns to glob patterns
8898
9285
  throw new RuleConversionError('Conversion of RegExp patterns is not yet supported');
8899
9286
  }
8900
- convertedSelector.children.push(CssTree.createAttributeSelectorNode(TAG_CONTENT, arg.value));
9287
+ convertedSelector.children.push(CssTree.createAttributeSelectorNode(AttributeSelectors.TagContent, arg.value));
8901
9288
  break;
8902
9289
  // https://github.com/gorhill/uBlock/wiki/Procedural-cosmetic-filters#subjectmin-text-lengthn
8903
- case MIN_TEXT_LENGTH:
9290
+ case PseudoClasses$1.MinTextLength:
8904
9291
  minLength = CssTree.parsePseudoClassArgumentAsNumber(node);
8905
9292
  break;
8906
9293
  default:
@@ -8912,10 +9299,10 @@ class HtmlRuleConverter extends RuleConverterBase {
8912
9299
  }
8913
9300
  }
8914
9301
  if (minLength !== NOT_SPECIFIED) {
8915
- convertedSelector.children.push(CssTree.createAttributeSelectorNode(MIN_LENGTH, String(minLength)));
9302
+ convertedSelector.children.push(CssTree.createAttributeSelectorNode(AttributeSelectors.MinLength, String(minLength)));
8916
9303
  }
8917
- convertedSelector.children.push(CssTree.createAttributeSelectorNode(MAX_LENGTH, String(maxLength === NOT_SPECIFIED
8918
- ? ADGUARD_HTML_CONVERSION_MAX_LENGTH
9304
+ convertedSelector.children.push(CssTree.createAttributeSelectorNode(AttributeSelectors.MaxLength, String(maxLength === NOT_SPECIFIED
9305
+ ? ADG_HTML_CONVERSION_MAX_LENGTH
8919
9306
  : maxLength)));
8920
9307
  // Create the converted rule
8921
9308
  conversionResult.push({
@@ -8925,7 +9312,7 @@ class HtmlRuleConverter extends RuleConverterBase {
8925
9312
  // Convert the separator based on the exception status
8926
9313
  separator: {
8927
9314
  type: 'Value',
8928
- value: ruleNode.exception
9315
+ value: rule.exception
8929
9316
  ? exports.CosmeticRuleSeparator.AdgHtmlFilteringException
8930
9317
  : exports.CosmeticRuleSeparator.AdgHtmlFiltering,
8931
9318
  },
@@ -8940,11 +9327,11 @@ class HtmlRuleConverter extends RuleConverterBase {
8940
9327
  }],
8941
9328
  },
8942
9329
  },
8943
- exception: ruleNode.exception,
8944
- domains: ruleNode.domains,
9330
+ exception: rule.exception,
9331
+ domains: cloneDomainListNode(rule.domains),
8945
9332
  });
8946
9333
  }
8947
- return conversionResult;
9334
+ return createNodeConversionResult(conversionResult, true);
8948
9335
  }
8949
9336
  }
8950
9337
 
@@ -8965,96 +9352,38 @@ function getScriptletName(scriptletNode) {
8965
9352
  return scriptletNode.children[0].value;
8966
9353
  }
8967
9354
  /**
8968
- * Set name of the scriptlet
9355
+ * Set name of the scriptlet.
9356
+ * Modifies input `scriptletNode` if needed.
8969
9357
  *
8970
9358
  * @param scriptletNode Scriptlet node to set name of
8971
9359
  * @param name Name to set
8972
- * @returns Scriptlet node with the specified name
8973
- * @throws If the scriptlet is empty
8974
9360
  */
8975
9361
  function setScriptletName(scriptletNode, name) {
8976
- if (scriptletNode.children.length === 0) {
8977
- throw new Error('Empty scriptlet');
9362
+ if (scriptletNode.children.length > 0) {
9363
+ // eslint-disable-next-line no-param-reassign
9364
+ scriptletNode.children[0].value = name;
8978
9365
  }
8979
- const scriptletNodeClone = cloneDeep(scriptletNode);
8980
- scriptletNodeClone.children[0].value = name;
8981
- return scriptletNodeClone;
8982
9366
  }
8983
9367
  /**
8984
9368
  * Set quote type of the scriptlet parameters
8985
9369
  *
8986
9370
  * @param scriptletNode Scriptlet node to set quote type of
8987
9371
  * @param quoteType Preferred quote type
8988
- * @returns Scriptlet node with the specified quote type
8989
9372
  */
8990
9373
  function setScriptletQuoteType(scriptletNode, quoteType) {
8991
- if (scriptletNode.children.length === 0) {
8992
- throw new Error('Empty scriptlet');
8993
- }
8994
- const scriptletNodeClone = cloneDeep(scriptletNode);
8995
- for (let i = 0; i < scriptletNodeClone.children.length; i += 1) {
8996
- scriptletNodeClone.children[i].value = QuoteUtils.setStringQuoteType(scriptletNodeClone.children[i].value, quoteType);
8997
- }
8998
- return scriptletNodeClone;
8999
- }
9000
-
9001
- /**
9002
- * @file Scriptlet conversions from ABP and uBO to ADG
9003
- */
9004
- const ABP_SCRIPTLET_PREFIX = 'abp-';
9005
- const UBO_SCRIPTLET_PREFIX = 'ubo-';
9006
- /**
9007
- * Helper class for converting scriptlets from ABP and uBO to ADG
9008
- */
9009
- class AdgScriptletConverter {
9010
- /**
9011
- * Helper function to convert scriptlets to ADG. We implement the core
9012
- * logic here to avoid code duplication.
9013
- *
9014
- * @param scriptletNode Scriptlet parameter list node to convert
9015
- * @param prefix Prefix to add to the scriptlet name
9016
- * @returns Converted scriptlet parameter list node
9017
- */
9018
- static convertToAdg(scriptletNode, prefix) {
9019
- // Remove possible quotes just to make it easier to work with the scriptlet name
9020
- const scriptletName = QuoteUtils.setStringQuoteType(getScriptletName(scriptletNode), exports.QuoteType.None);
9021
- // Clone the node to avoid any side effects
9022
- let result = cloneDeep(scriptletNode);
9023
- // Only add prefix if it's not already there
9024
- if (!scriptletName.startsWith(prefix)) {
9025
- result = setScriptletName(scriptletNode, `${prefix}${scriptletName}`);
9374
+ if (scriptletNode.children.length > 0) {
9375
+ for (let i = 0; i < scriptletNode.children.length; i += 1) {
9376
+ // eslint-disable-next-line no-param-reassign
9377
+ scriptletNode.children[i].value = QuoteUtils.setStringQuoteType(scriptletNode.children[i].value, quoteType);
9026
9378
  }
9027
- // ADG scriptlet parameters should be quoted, and single quoted are preferred
9028
- result = setScriptletQuoteType(result, exports.QuoteType.Single);
9029
- return result;
9030
9379
  }
9031
- /**
9032
- * Converts an ABP snippet node to ADG scriptlet node, if possible.
9033
- *
9034
- * @param scriptletNode Scriptlet node to convert
9035
- * @returns Converted scriptlet node
9036
- * @throws If the scriptlet isn't supported by ADG or is invalid
9037
- * @see {@link https://help.adblockplus.org/hc/en-us/articles/1500002338501#snippets-ref}
9038
- */
9039
- static convertFromAbp = (scriptletNode) => {
9040
- return AdgScriptletConverter.convertToAdg(scriptletNode, ABP_SCRIPTLET_PREFIX);
9041
- };
9042
- /**
9043
- * Convert a uBO scriptlet node to ADG scriptlet node, if possible.
9044
- *
9045
- * @param scriptletNode Scriptlet node to convert
9046
- * @returns Converted scriptlet node
9047
- * @throws If the scriptlet isn't supported by ADG or is invalid
9048
- * @see {@link https://github.com/gorhill/uBlock/wiki/Resources-Library#available-general-purpose-scriptlets}
9049
- */
9050
- static convertFromUbo = (scriptletNode) => {
9051
- return AdgScriptletConverter.convertToAdg(scriptletNode, UBO_SCRIPTLET_PREFIX);
9052
- };
9053
9380
  }
9054
9381
 
9055
9382
  /**
9056
9383
  * @file Scriptlet injection rule converter
9057
9384
  */
9385
+ const ABP_SCRIPTLET_PREFIX = 'abp-';
9386
+ const UBO_SCRIPTLET_PREFIX = 'ubo-';
9058
9387
  /**
9059
9388
  * Scriptlet injection rule converter class
9060
9389
  *
@@ -9065,38 +9394,91 @@ class ScriptletRuleConverter extends RuleConverterBase {
9065
9394
  * Converts a scriptlet injection rule to AdGuard format, if possible.
9066
9395
  *
9067
9396
  * @param rule Rule node to convert
9068
- * @returns Array of converted rule nodes
9397
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
9398
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
9399
+ * If the rule was not converted, the result array will contain the original node with the same object reference
9069
9400
  * @throws If the rule is invalid or cannot be converted
9070
9401
  */
9071
9402
  static convertToAdg(rule) {
9072
- // Clone the provided AST node to avoid side effects
9073
- const ruleNode = cloneDeep(rule);
9403
+ // Ignore AdGuard rules
9404
+ if (rule.syntax === exports.AdblockSyntax.Adg) {
9405
+ return createNodeConversionResult([rule], false);
9406
+ }
9407
+ const separator = rule.separator.value;
9408
+ let convertedSeparator = separator;
9409
+ convertedSeparator = rule.exception
9410
+ ? exports.CosmeticRuleSeparator.AdgJsInjectionException
9411
+ : exports.CosmeticRuleSeparator.AdgJsInjection;
9074
9412
  const convertedScriptlets = [];
9075
- for (const scriptlet of ruleNode.body.children) {
9076
- if (ruleNode.syntax === exports.AdblockSyntax.Abp) {
9077
- convertedScriptlets.push(AdgScriptletConverter.convertFromAbp(scriptlet));
9078
- }
9079
- else if (ruleNode.syntax === exports.AdblockSyntax.Ubo) {
9080
- convertedScriptlets.push(AdgScriptletConverter.convertFromUbo(scriptlet));
9413
+ for (const scriptlet of rule.body.children) {
9414
+ // Clone the node to avoid any side effects
9415
+ const scriptletClone = cloneScriptletRuleNode(scriptlet);
9416
+ // Remove possible quotes just to make it easier to work with the scriptlet name
9417
+ const scriptletName = QuoteUtils.setStringQuoteType(getScriptletName(scriptletClone), exports.QuoteType.None);
9418
+ // Add prefix if it's not already there
9419
+ let prefix;
9420
+ switch (rule.syntax) {
9421
+ case exports.AdblockSyntax.Abp:
9422
+ prefix = ABP_SCRIPTLET_PREFIX;
9423
+ break;
9424
+ case exports.AdblockSyntax.Ubo:
9425
+ prefix = UBO_SCRIPTLET_PREFIX;
9426
+ break;
9427
+ default:
9428
+ prefix = EMPTY;
9081
9429
  }
9082
- else if (ruleNode.syntax === exports.AdblockSyntax.Adg) {
9083
- convertedScriptlets.push(scriptlet);
9430
+ if (!scriptletName.startsWith(prefix)) {
9431
+ setScriptletName(scriptletClone, `${prefix}${scriptletName}`);
9084
9432
  }
9433
+ // ADG scriptlet parameters should be quoted, and single quoted are preferred
9434
+ setScriptletQuoteType(scriptletClone, exports.QuoteType.Single);
9435
+ convertedScriptlets.push(scriptletClone);
9085
9436
  }
9086
- ruleNode.separator.value = ruleNode.exception
9087
- ? exports.CosmeticRuleSeparator.AdgJsInjectionException
9088
- : exports.CosmeticRuleSeparator.AdgJsInjection;
9089
- // ADG doesn't support multiple scriptlets in one rule, so we should split them
9090
- return convertedScriptlets.map((scriptlet) => {
9091
- return {
9092
- ...ruleNode,
9437
+ return createNodeConversionResult(convertedScriptlets.map((scriptlet) => {
9438
+ const res = {
9439
+ category: rule.category,
9440
+ type: rule.type,
9093
9441
  syntax: exports.AdblockSyntax.Adg,
9442
+ exception: rule.exception,
9443
+ domains: cloneDomainListNode(rule.domains),
9444
+ separator: {
9445
+ type: 'Value',
9446
+ value: convertedSeparator,
9447
+ },
9094
9448
  body: {
9095
- ...ruleNode.body,
9449
+ type: rule.body.type,
9096
9450
  children: [scriptlet],
9097
9451
  },
9098
9452
  };
9099
- });
9453
+ if (rule.modifiers) {
9454
+ res.modifiers = cloneModifierListNode(rule.modifiers);
9455
+ }
9456
+ return res;
9457
+ }), true);
9458
+ }
9459
+ }
9460
+
9461
+ /**
9462
+ * A very simple map extension that allows to store multiple values for the same key
9463
+ * by storing them in an array.
9464
+ *
9465
+ * @todo Add more methods if needed
9466
+ */
9467
+ class MultiValueMap extends Map {
9468
+ /**
9469
+ * Adds a value to the map. If the key already exists, the value will be appended to the existing array,
9470
+ * otherwise a new array will be created for the key.
9471
+ *
9472
+ * @param key Key to add
9473
+ * @param values Value(s) to add
9474
+ */
9475
+ add(key, ...values) {
9476
+ let currentValues = super.get(key);
9477
+ if (isUndefined(currentValues)) {
9478
+ currentValues = [];
9479
+ super.set(key, values);
9480
+ }
9481
+ currentValues.push(...values);
9100
9482
  }
9101
9483
  }
9102
9484
 
@@ -9122,69 +9504,115 @@ class AdgCosmeticRuleModifierConverter {
9122
9504
  * Converts a uBO cosmetic rule modifier list to ADG, if possible.
9123
9505
  *
9124
9506
  * @param modifierList Cosmetic rule modifier list node to convert
9125
- * @returns Converted cosmetic rule modifier list node
9507
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
9508
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
9509
+ * If the node was not converted, the result will contain the original node with the same object reference
9126
9510
  * @throws If the modifier list cannot be converted
9127
9511
  * @see {@link https://github.com/gorhill/uBlock/wiki/Procedural-cosmetic-filters#cosmetic-filter-operators}
9128
9512
  */
9129
- static convertFromUbo = (modifierList) => {
9130
- const convertedModifierList = createModifierListNode();
9131
- for (const modifier of modifierList.children) {
9132
- let modifierValue;
9133
- switch (modifier.modifier.value) {
9134
- case UBO_MATCHES_PATH_OPERATOR:
9135
- // :matches-path() should have a value
9136
- if (!modifier.value) {
9137
- throw new RuleConversionError('Missing value for :matches-path(...)');
9138
- }
9139
- modifierValue = RegExpUtils.isRegexPattern(modifier.value.value)
9140
- ? StringUtils.escapeCharacters(modifier.value.value, SPECIAL_MODIFIER_REGEX_CHARS)
9141
- : modifier.value.value;
9142
- // Convert uBO's `:matches-path(...)` operator to ADG's `$path=...` modifier
9143
- convertedModifierList.children.push(createModifierNode(ADG_PATH_MODIFIER,
9144
- // We should negate the regexp if the modifier is an exception
9145
- modifier.exception
9146
- // eslint-disable-next-line max-len
9147
- ? `${REGEX_MARKER}${RegExpUtils.negateRegexPattern(RegExpUtils.patternToRegexp(modifierValue))}${REGEX_MARKER}`
9148
- : modifierValue));
9149
- break;
9150
- default:
9151
- // Leave the modifier as-is
9152
- convertedModifierList.children.push(modifier);
9513
+ static convertFromUbo(modifierList) {
9514
+ const conversionMap = new MultiValueMap();
9515
+ modifierList.children.forEach((modifier, index) => {
9516
+ // :matches-path
9517
+ if (modifier.modifier.value === UBO_MATCHES_PATH_OPERATOR) {
9518
+ if (!modifier.value) {
9519
+ throw new RuleConversionError(`'${UBO_MATCHES_PATH_OPERATOR}' operator requires a value`);
9520
+ }
9521
+ const value = RegExpUtils.isRegexPattern(modifier.value.value)
9522
+ ? StringUtils.escapeCharacters(modifier.value.value, SPECIAL_MODIFIER_REGEX_CHARS)
9523
+ : modifier.value.value;
9524
+ // Convert uBO's `:matches-path(...)` operator to ADG's `$path=...` modifier
9525
+ conversionMap.add(index, createModifierNode(ADG_PATH_MODIFIER,
9526
+ // We should negate the regexp if the modifier is an exception
9527
+ modifier.exception
9528
+ // eslint-disable-next-line max-len
9529
+ ? `${REGEX_MARKER}${RegExpUtils.negateRegexPattern(RegExpUtils.patternToRegexp(value))}${REGEX_MARKER}`
9530
+ : value));
9153
9531
  }
9154
- }
9155
- return convertedModifierList;
9156
- };
9532
+ });
9533
+ // Check if we have any converted modifiers
9534
+ if (conversionMap.size) {
9535
+ const modifierListClone = clone(modifierList);
9536
+ // Replace the original modifiers with the converted ones
9537
+ modifierListClone.children = modifierListClone.children.map((modifier, index) => {
9538
+ const convertedModifier = conversionMap.get(index);
9539
+ return convertedModifier ?? modifier;
9540
+ }).flat();
9541
+ return createConversionResult(modifierListClone, true);
9542
+ }
9543
+ // Otherwise, just return the original modifier list
9544
+ return createConversionResult(modifierList, false);
9545
+ }
9157
9546
  }
9158
9547
 
9159
- // Constants for pseudo-classes (please keep them sorted alphabetically)
9160
- const ABP_CONTAINS = '-abp-contains';
9161
- const ABP_HAS = '-abp-has';
9162
- const CONTAINS = 'contains';
9163
- const HAS = 'has';
9164
- const HAS_TEXT = 'has-text';
9165
- const MATCHES_CSS = 'matches-css';
9166
- const MATCHES_CSS_AFTER = 'matches-css-after';
9167
- const MATCHES_CSS_BEFORE = 'matches-css-before';
9168
- const NOT = 'not';
9169
- // Constants for pseudo-elements (please keep them sorted alphabetically)
9170
- const AFTER = 'after';
9171
- const BEFORE = 'before';
9548
+ var PseudoClasses;
9549
+ (function (PseudoClasses) {
9550
+ PseudoClasses["AbpContains"] = "-abp-contains";
9551
+ PseudoClasses["AbpHas"] = "-abp-has";
9552
+ PseudoClasses["Contains"] = "contains";
9553
+ PseudoClasses["Has"] = "has";
9554
+ PseudoClasses["HasText"] = "has-text";
9555
+ PseudoClasses["MatchesCss"] = "matches-css";
9556
+ PseudoClasses["MatchesCssAfter"] = "matches-css-after";
9557
+ PseudoClasses["MatchesCssBefore"] = "matches-css-before";
9558
+ PseudoClasses["Not"] = "not";
9559
+ })(PseudoClasses || (PseudoClasses = {}));
9560
+ var PseudoElements;
9561
+ (function (PseudoElements) {
9562
+ PseudoElements["After"] = "after";
9563
+ PseudoElements["Before"] = "before";
9564
+ })(PseudoElements || (PseudoElements = {}));
9565
+ const PSEUDO_ELEMENT_NAMES = new Set([
9566
+ PseudoElements.After,
9567
+ PseudoElements.Before,
9568
+ ]);
9569
+ const LEGACY_MATCHES_CSS_NAMES = new Set([
9570
+ PseudoClasses.MatchesCssAfter,
9571
+ PseudoClasses.MatchesCssBefore,
9572
+ ]);
9573
+ const LEGACY_EXT_CSS_INDICATOR_PSEUDO_NAMES = new Set([
9574
+ PseudoClasses.Not,
9575
+ PseudoClasses.MatchesCssBefore,
9576
+ PseudoClasses.MatchesCssAfter,
9577
+ ]);
9578
+ const CSS_CONVERSION_INDICATOR_PSEUDO_NAMES = new Set([
9579
+ PseudoClasses.AbpContains,
9580
+ PseudoClasses.AbpHas,
9581
+ PseudoClasses.HasText,
9582
+ ]);
9172
9583
  /**
9173
9584
  * Converts some pseudo-classes to pseudo-elements. For example:
9174
9585
  * - `:before` → `::before`
9175
9586
  *
9176
9587
  * @param selectorList Selector list to convert
9177
- * @returns Converted selector list
9588
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
9589
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
9590
+ * If the node was not converted, the result will contain the original node with the same object reference
9178
9591
  */
9179
9592
  function convertToPseudoElements(selectorList) {
9180
- // Prepare conversion result
9181
- const selectorListClone = cloneDeep(selectorList);
9593
+ // Check conversion indications before doing any heavy work
9594
+ const hasIndicator = ecssTree.find(
9595
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
9596
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9597
+ selectorList, (node) => node.type === exports.CssTreeNodeType.PseudoClassSelector && PSEUDO_ELEMENT_NAMES.has(node.name));
9598
+ if (!hasIndicator) {
9599
+ return createConversionResult(selectorList, false);
9600
+ }
9601
+ // Make a clone of the selector list to avoid modifying the original one,
9602
+ // then convert & return the cloned version
9603
+ const selectorListClone = clone(selectorList);
9604
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
9605
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9182
9606
  ecssTree.walk(selectorListClone, {
9607
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
9608
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9183
9609
  leave: (node) => {
9184
9610
  if (node.type === exports.CssTreeNodeType.PseudoClassSelector) {
9185
- // :after ::after
9186
- // :before ::before
9187
- if (node.name === AFTER || node.name === BEFORE) {
9611
+ // If the pseudo-class is `:before` or `:after`, then we should
9612
+ // convert the node type to pseudo-element:
9613
+ // :after → ::after
9614
+ // :before → ::before
9615
+ if (PSEUDO_ELEMENT_NAMES.has(node.name)) {
9188
9616
  Object.assign(node, {
9189
9617
  ...node,
9190
9618
  type: exports.CssTreeNodeType.PseudoElementSelector,
@@ -9193,7 +9621,7 @@ function convertToPseudoElements(selectorList) {
9193
9621
  }
9194
9622
  },
9195
9623
  });
9196
- return selectorListClone;
9624
+ return createConversionResult(selectorListClone, true);
9197
9625
  }
9198
9626
  /**
9199
9627
  * Converts legacy Extended CSS `matches-css-before` and `matches-css-after`
@@ -9202,33 +9630,36 @@ function convertToPseudoElements(selectorList) {
9202
9630
  * - `:matches-css-after(...)` → `:matches-css(after, ...)`
9203
9631
  *
9204
9632
  * @param node Node to convert
9633
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
9634
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
9635
+ * If the node was not converted, the result will contain the original node with the same object reference
9205
9636
  * @throws If the node is invalid
9206
9637
  */
9207
9638
  function convertLegacyMatchesCss(node) {
9208
- const nodeClone = cloneDeep(node);
9209
- if (nodeClone.type === exports.CssTreeNodeType.PseudoClassSelector
9210
- && [MATCHES_CSS_BEFORE, MATCHES_CSS_AFTER].includes(nodeClone.name)) {
9211
- if (!nodeClone.children || nodeClone.children.size < 1) {
9212
- throw new Error(`Invalid ${nodeClone.name} pseudo-class: missing argument`);
9213
- }
9214
- // Remove the 'matches-css-' prefix to get the direction
9215
- const direction = nodeClone.name.substring(MATCHES_CSS.length + 1);
9216
- // Rename the pseudo-class
9217
- nodeClone.name = MATCHES_CSS;
9218
- // Add the direction to the first raw argument
9219
- const arg = nodeClone.children.first;
9220
- // Check argument
9221
- if (!arg) {
9222
- throw new Error(`Invalid ${nodeClone.name} pseudo-class: argument shouldn't be null`);
9223
- }
9224
- if (arg.type !== exports.CssTreeNodeType.Raw) {
9225
- throw new Error(`Invalid ${nodeClone.name} pseudo-class: unexpected argument type`);
9226
- }
9227
- // Add the direction as the first argument
9228
- arg.value = `${direction},${arg.value}`;
9229
- // Replace the original node with the converted one
9230
- Object.assign(node, nodeClone);
9231
- }
9639
+ // Check conversion indications before doing any heavy work
9640
+ if (node.type !== exports.CssTreeNodeType.PseudoClassSelector || !LEGACY_MATCHES_CSS_NAMES.has(node.name)) {
9641
+ return createConversionResult(node, false);
9642
+ }
9643
+ const nodeClone = clone(node);
9644
+ if (!nodeClone.children || nodeClone.children.length < 1) {
9645
+ throw new Error(`Invalid ${node.name} pseudo-class: missing argument`);
9646
+ }
9647
+ // Rename the pseudo-class
9648
+ nodeClone.name = PseudoClasses.MatchesCss;
9649
+ // Remove the 'matches-css-' prefix to get the direction
9650
+ const direction = node.name.substring(PseudoClasses.MatchesCss.length + 1);
9651
+ // Add the direction to the first raw argument
9652
+ const arg = nodeClone.children[0];
9653
+ // Check argument
9654
+ if (!arg) {
9655
+ throw new Error(`Invalid ${node.name} pseudo-class: argument shouldn't be null`);
9656
+ }
9657
+ if (arg.type !== exports.CssTreeNodeType.Raw) {
9658
+ throw new Error(`Invalid ${node.name} pseudo-class: unexpected argument type`);
9659
+ }
9660
+ // Add the direction as the first argument
9661
+ arg.value = `${direction},${arg.value}`;
9662
+ return createConversionResult(nodeClone, true);
9232
9663
  }
9233
9664
  /**
9234
9665
  * Converts legacy Extended CSS selectors to the modern Extended CSS syntax.
@@ -9238,16 +9669,40 @@ function convertLegacyMatchesCss(node) {
9238
9669
  * - `[-ext-matches-css-before=...]` → `:matches-css(before, ...)`
9239
9670
  *
9240
9671
  * @param selectorList Selector list AST to convert
9241
- * @returns Converted selector list
9672
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
9673
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
9674
+ * If the node was not converted, the result will contain the original node with the same object reference
9242
9675
  */
9243
9676
  function convertFromLegacyExtendedCss(selectorList) {
9244
- // Prepare conversion result
9245
- const selectorListClone = cloneDeep(selectorList);
9677
+ // Check conversion indications before doing any heavy work
9678
+ const hasIndicator = ecssTree.find(
9679
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
9680
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9681
+ selectorList, (node) => {
9682
+ if (node.type === exports.CssTreeNodeType.PseudoClassSelector) {
9683
+ return LEGACY_EXT_CSS_INDICATOR_PSEUDO_NAMES.has(node.name);
9684
+ }
9685
+ if (node.type === exports.CssTreeNodeType.AttributeSelector) {
9686
+ return node.name.name.startsWith(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX);
9687
+ }
9688
+ return false;
9689
+ });
9690
+ if (!hasIndicator) {
9691
+ return createConversionResult(selectorList, false);
9692
+ }
9693
+ const selectorListClone = clone(selectorList);
9694
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
9695
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9246
9696
  ecssTree.walk(selectorListClone, {
9697
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
9698
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9247
9699
  leave: (node) => {
9248
9700
  // :matches-css-before(arg) → :matches-css(before,arg)
9249
9701
  // :matches-css-after(arg) → :matches-css(after,arg)
9250
- convertLegacyMatchesCss(node);
9702
+ const convertedLegacyExtCss = convertLegacyMatchesCss(node);
9703
+ if (convertedLegacyExtCss.isConverted) {
9704
+ Object.assign(node, convertedLegacyExtCss.result);
9705
+ }
9251
9706
  // [-ext-name=...] → :name(...)
9252
9707
  // [-ext-name='...'] → :name(...)
9253
9708
  // [-ext-name="..."] → :name(...)
@@ -9261,7 +9716,7 @@ function convertFromLegacyExtendedCss(selectorList) {
9261
9716
  // Remove the '-ext-' prefix to get the pseudo-class name
9262
9717
  const name = node.name.name.substring(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX.length);
9263
9718
  // Prepare the children list for the pseudo-class node
9264
- const children = new ecssTree.List();
9719
+ const children = [];
9265
9720
  // TODO: Change String node to Raw node to drop the quotes.
9266
9721
  // The structure of the node is the same, just the type
9267
9722
  // is different and generate() will generate the quotes
@@ -9274,7 +9729,7 @@ function convertFromLegacyExtendedCss(selectorList) {
9274
9729
  // For example, if the input is [-ext-has="> .selector"], then
9275
9730
  // we need to parse "> .selector" as a selector instead of string
9276
9731
  // it as a raw value
9277
- if ([HAS, NOT].includes(name)) {
9732
+ if ([PseudoClasses.Has, PseudoClasses.Not].includes(name)) {
9278
9733
  // Get the value of the attribute selector
9279
9734
  const { value } = node;
9280
9735
  // If the value is an identifier, then simply push it to the
@@ -9284,10 +9739,12 @@ function convertFromLegacyExtendedCss(selectorList) {
9284
9739
  }
9285
9740
  else if (value.type === exports.CssTreeNodeType.String) {
9286
9741
  // Parse the value as a selector
9287
- const parsedChildren = CssTree.parse(value.value, exports.CssTreeParserContext.selectorList);
9742
+ const parsedChildren = CssTree.parsePlain(value.value, exports.CssTreeParserContext.selectorList);
9288
9743
  // Don't forget convert the parsed AST again, because
9289
9744
  // it was a raw string before
9290
- children.push(convertFromLegacyExtendedCss(parsedChildren));
9745
+ const convertedChildren = convertFromLegacyExtendedCss(parsedChildren);
9746
+ // Push the converted children to the list
9747
+ children.push(convertedChildren.result);
9291
9748
  }
9292
9749
  }
9293
9750
  else {
@@ -9314,14 +9771,12 @@ function convertFromLegacyExtendedCss(selectorList) {
9314
9771
  children,
9315
9772
  };
9316
9773
  // Handle this case: [-ext-matches-css-before=...] → :matches-css(before,...)
9317
- convertLegacyMatchesCss(pseudoNode);
9318
- // Convert attribute selector to pseudo-class selector, but
9319
- // keep the reference to the original node
9320
- Object.assign(node, pseudoNode);
9774
+ const convertedPseudoNode = convertLegacyMatchesCss(pseudoNode);
9775
+ Object.assign(node, convertedPseudoNode.isConverted ? convertedPseudoNode.result : pseudoNode);
9321
9776
  }
9322
9777
  },
9323
9778
  });
9324
- return selectorListClone;
9779
+ return createConversionResult(selectorListClone, true);
9325
9780
  }
9326
9781
  /**
9327
9782
  * CSS selector converter
@@ -9333,32 +9788,51 @@ class CssSelectorConverter extends ConverterBase {
9333
9788
  * Converts Extended CSS elements to AdGuard-compatible ones
9334
9789
  *
9335
9790
  * @param selectorList Selector list to convert
9336
- * @returns Converted selector list
9791
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
9792
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
9793
+ * If the node was not converted, the result will contain the original node with the same object reference
9337
9794
  * @throws If the rule is invalid or incompatible
9338
9795
  */
9339
9796
  static convertToAdg(selectorList) {
9340
9797
  // First, convert
9341
9798
  // - legacy Extended CSS selectors to the modern Extended CSS syntax and
9342
9799
  // - some pseudo-classes to pseudo-elements
9343
- const selectorListClone = convertToPseudoElements(convertFromLegacyExtendedCss(cloneDeep(selectorList)));
9800
+ const legacyExtCssConverted = convertFromLegacyExtendedCss(selectorList);
9801
+ const pseudoElementsConverted = convertToPseudoElements(legacyExtCssConverted.result);
9802
+ const hasIndicator = legacyExtCssConverted.isConverted || pseudoElementsConverted.isConverted || ecssTree.find(
9803
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
9804
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9805
+ selectorList,
9806
+ // eslint-disable-next-line max-len
9807
+ (node) => node.type === exports.CssTreeNodeType.PseudoClassSelector && CSS_CONVERSION_INDICATOR_PSEUDO_NAMES.has(node.name));
9808
+ if (!hasIndicator) {
9809
+ return createConversionResult(selectorList, false);
9810
+ }
9811
+ const selectorListClone = legacyExtCssConverted.isConverted || pseudoElementsConverted.isConverted
9812
+ ? pseudoElementsConverted.result
9813
+ : clone(selectorList);
9344
9814
  // Then, convert some Extended CSS pseudo-classes to AdGuard-compatible ones
9815
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
9816
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9345
9817
  ecssTree.walk(selectorListClone, {
9818
+ // TODO: Need to improve CSSTree types, until then we need to use any type here
9819
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9346
9820
  leave: (node) => {
9347
9821
  if (node.type === exports.CssTreeNodeType.PseudoClassSelector) {
9348
9822
  // :-abp-contains(...) → :contains(...)
9349
9823
  // :has-text(...) → :contains(...)
9350
- if (node.name === ABP_CONTAINS || node.name === HAS_TEXT) {
9351
- CssTree.renamePseudoClass(node, CONTAINS);
9824
+ if (node.name === PseudoClasses.AbpContains || node.name === PseudoClasses.HasText) {
9825
+ CssTree.renamePseudoClass(node, PseudoClasses.Contains);
9352
9826
  }
9353
9827
  // :-abp-has(...) → :has(...)
9354
- if (node.name === ABP_HAS) {
9355
- CssTree.renamePseudoClass(node, HAS);
9828
+ if (node.name === PseudoClasses.AbpHas) {
9829
+ CssTree.renamePseudoClass(node, PseudoClasses.Has);
9356
9830
  }
9357
9831
  // TODO: check uBO's `:others()` and `:watch-attr()` pseudo-classes
9358
9832
  }
9359
9833
  },
9360
9834
  });
9361
- return selectorListClone;
9835
+ return createConversionResult(selectorListClone, true);
9362
9836
  }
9363
9837
  }
9364
9838
 
@@ -9375,27 +9849,39 @@ class CssInjectionRuleConverter extends RuleConverterBase {
9375
9849
  * Converts a CSS injection rule to AdGuard format, if possible.
9376
9850
  *
9377
9851
  * @param rule Rule node to convert
9378
- * @returns Array of converted rule nodes
9852
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
9853
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
9854
+ * If the rule was not converted, the result array will contain the original node with the same object reference
9379
9855
  * @throws If the rule is invalid or cannot be converted
9380
9856
  */
9381
9857
  static convertToAdg(rule) {
9382
- // Clone the provided AST node to avoid side effects
9383
- const ruleNode = cloneDeep(rule);
9858
+ const separator = rule.separator.value;
9859
+ let convertedSeparator = separator;
9384
9860
  // Change the separator if the rule contains ExtendedCSS selectors
9385
- if (CssTree.hasAnySelectorExtendedCssNode(ruleNode.body.selectorList) || ruleNode.body.remove) {
9386
- ruleNode.separator.value = ruleNode.exception
9861
+ if (CssTree.hasAnySelectorExtendedCssNode(rule.body.selectorList) || rule.body.remove) {
9862
+ convertedSeparator = rule.exception
9387
9863
  ? exports.CosmeticRuleSeparator.AdgExtendedCssInjectionException
9388
9864
  : exports.CosmeticRuleSeparator.AdgExtendedCssInjection;
9389
9865
  }
9390
9866
  else {
9391
- ruleNode.separator.value = ruleNode.exception
9867
+ convertedSeparator = rule.exception
9392
9868
  ? exports.CosmeticRuleSeparator.AdgCssInjectionException
9393
9869
  : exports.CosmeticRuleSeparator.AdgCssInjection;
9394
9870
  }
9395
- // Convert CSS selector list
9396
- Object.assign(ruleNode.body.selectorList, CssSelectorConverter.convertToAdg(ecssTree.fromPlainObject(ruleNode.body.selectorList)));
9397
- ruleNode.syntax = exports.AdblockSyntax.Adg;
9398
- return [ruleNode];
9871
+ const convertedSelectorList = CssSelectorConverter.convertToAdg(rule.body.selectorList);
9872
+ // Check if the rule needs to be converted
9873
+ if (!(rule.syntax === exports.AdblockSyntax.Common || rule.syntax === exports.AdblockSyntax.Adg)
9874
+ || separator !== convertedSeparator
9875
+ || convertedSelectorList.isConverted) {
9876
+ // TODO: Replace with custom clone method
9877
+ const ruleClone = clone(rule);
9878
+ ruleClone.syntax = exports.AdblockSyntax.Adg;
9879
+ ruleClone.separator.value = convertedSeparator;
9880
+ ruleClone.body.selectorList = convertedSelectorList.result;
9881
+ return createNodeConversionResult([ruleClone], true);
9882
+ }
9883
+ // Otherwise, return the original rule
9884
+ return createNodeConversionResult([rule], false);
9399
9885
  }
9400
9886
  }
9401
9887
 
@@ -9412,27 +9898,39 @@ class ElementHidingRuleConverter extends RuleConverterBase {
9412
9898
  * Converts an element hiding rule to AdGuard format, if possible.
9413
9899
  *
9414
9900
  * @param rule Rule node to convert
9415
- * @returns Array of converted rule nodes
9901
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
9902
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
9903
+ * If the rule was not converted, the result array will contain the original node with the same object reference
9416
9904
  * @throws If the rule is invalid or cannot be converted
9417
9905
  */
9418
9906
  static convertToAdg(rule) {
9419
- // Clone the provided AST node to avoid side effects
9420
- const ruleNode = cloneDeep(rule);
9907
+ const separator = rule.separator.value;
9908
+ let convertedSeparator = separator;
9421
9909
  // Change the separator if the rule contains ExtendedCSS selectors
9422
- if (CssTree.hasAnySelectorExtendedCssNode(ruleNode.body.selectorList)) {
9423
- ruleNode.separator.value = ruleNode.exception
9910
+ if (CssTree.hasAnySelectorExtendedCssNode(rule.body.selectorList)) {
9911
+ convertedSeparator = rule.exception
9424
9912
  ? exports.CosmeticRuleSeparator.ExtendedElementHidingException
9425
9913
  : exports.CosmeticRuleSeparator.ExtendedElementHiding;
9426
9914
  }
9427
9915
  else {
9428
- ruleNode.separator.value = ruleNode.exception
9916
+ convertedSeparator = rule.exception
9429
9917
  ? exports.CosmeticRuleSeparator.ElementHidingException
9430
9918
  : exports.CosmeticRuleSeparator.ElementHiding;
9431
9919
  }
9432
- // Convert CSS selector list
9433
- Object.assign(ruleNode.body.selectorList, CssSelectorConverter.convertToAdg(ecssTree.fromPlainObject(ruleNode.body.selectorList)));
9434
- ruleNode.syntax = exports.AdblockSyntax.Adg;
9435
- return [ruleNode];
9920
+ const convertedSelectorList = CssSelectorConverter.convertToAdg(rule.body.selectorList);
9921
+ // Check if the rule needs to be converted
9922
+ if (!(rule.syntax === exports.AdblockSyntax.Common || rule.syntax === exports.AdblockSyntax.Adg)
9923
+ || separator !== convertedSeparator
9924
+ || convertedSelectorList.isConverted) {
9925
+ // TODO: Replace with custom clone method
9926
+ const ruleClone = clone(rule);
9927
+ ruleClone.syntax = exports.AdblockSyntax.Adg;
9928
+ ruleClone.separator.value = convertedSeparator;
9929
+ ruleClone.body.selectorList = convertedSelectorList.result;
9930
+ return createNodeConversionResult([ruleClone], true);
9931
+ }
9932
+ // Otherwise, return the original rule
9933
+ return createNodeConversionResult([rule], false);
9436
9934
  }
9437
9935
  }
9438
9936
 
@@ -9460,7 +9958,7 @@ function createNetworkRuleNode(pattern, modifiers = undefined, exception = false
9460
9958
  },
9461
9959
  };
9462
9960
  if (!isUndefined(modifiers)) {
9463
- result.modifiers = cloneDeep(modifiers);
9961
+ result.modifiers = clone(modifiers);
9464
9962
  }
9465
9963
  return result;
9466
9964
  }
@@ -9480,32 +9978,37 @@ class HeaderRemovalRuleConverter extends RuleConverterBase {
9480
9978
  * Converts a header removal rule to AdGuard syntax, if possible.
9481
9979
  *
9482
9980
  * @param rule Rule node to convert
9483
- * @returns Array of converted rule nodes
9981
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
9982
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
9983
+ * If the rule was not converted, the result array will contain the original node with the same object reference
9484
9984
  * @throws If the rule is invalid or cannot be converted
9985
+ * @example
9986
+ * If the input rule is:
9987
+ * ```adblock
9988
+ * example.com##^responseheader(header-name)
9989
+ * ```
9990
+ * The output will be:
9991
+ * ```adblock
9992
+ * ||example.com^$removeheader=header-name
9993
+ * ```
9485
9994
  */
9486
9995
  static convertToAdg(rule) {
9487
- // Clone the provided AST node to avoid side effects
9488
- const ruleNode = cloneDeep(rule);
9489
9996
  // TODO: Add support for ABP syntax once it starts supporting header removal rules
9490
- // Check the input rule
9491
- if (ruleNode.category !== exports.RuleCategory.Cosmetic
9492
- || ruleNode.type !== exports.CosmeticRuleType.HtmlFilteringRule
9493
- || ruleNode.body.body.type !== exports.CssTreeNodeType.Function
9494
- || ruleNode.body.body.name !== UBO_RESPONSEHEADER_MARKER) {
9495
- throw new RuleConversionError('Not a response header rule');
9997
+ // Leave the rule as is if it's not a header removal rule
9998
+ if (rule.category !== exports.RuleCategory.Cosmetic
9999
+ || rule.type !== exports.CosmeticRuleType.HtmlFilteringRule
10000
+ || rule.body.body.type !== exports.CssTreeNodeType.Function
10001
+ || rule.body.body.name !== UBO_RESPONSEHEADER_MARKER) {
10002
+ return createNodeConversionResult([rule], false);
9496
10003
  }
9497
10004
  // Prepare network rule pattern
9498
- let pattern = EMPTY;
9499
- if (ruleNode.domains.children.length === 1) {
10005
+ const pattern = [];
10006
+ if (rule.domains.children.length === 1) {
9500
10007
  // If the rule has only one domain, we can use a simple network rule pattern:
9501
10008
  // ||single-domain-from-the-rule^
9502
- pattern = [
9503
- ADBLOCK_URL_START,
9504
- ruleNode.domains.children[0].value,
9505
- ADBLOCK_URL_SEPARATOR,
9506
- ].join(EMPTY);
10009
+ pattern.push(ADBLOCK_URL_START, rule.domains.children[0].value, ADBLOCK_URL_SEPARATOR);
9507
10010
  }
9508
- else if (ruleNode.domains.children.length > 1) {
10011
+ else if (rule.domains.children.length > 1) {
9509
10012
  // TODO: Add support for multiple domains, for example:
9510
10013
  // example.com,example.org,example.net##^responseheader(header-name)
9511
10014
  // We should consider allowing $domain with $removeheader modifier,
@@ -9515,13 +10018,13 @@ class HeaderRemovalRuleConverter extends RuleConverterBase {
9515
10018
  }
9516
10019
  // Prepare network rule modifiers
9517
10020
  const modifiers = createModifierListNode();
9518
- modifiers.children.push(createModifierNode(ADG_REMOVEHEADER_MODIFIER, CssTree.generateFunctionValue(ecssTree.fromPlainObject(ruleNode.body.body))));
10021
+ modifiers.children.push(createModifierNode(ADG_REMOVEHEADER_MODIFIER, CssTree.generateFunctionPlainValue(rule.body.body)));
9519
10022
  // Construct the network rule
9520
- return [
9521
- createNetworkRuleNode(pattern, modifiers,
10023
+ return createNodeConversionResult([
10024
+ createNetworkRuleNode(pattern.join(EMPTY), modifiers,
9522
10025
  // Copy the exception flag
9523
- ruleNode.exception, exports.AdblockSyntax.Adg),
9524
- ];
10026
+ rule.exception, exports.AdblockSyntax.Adg),
10027
+ ], true);
9525
10028
  }
9526
10029
  }
9527
10030
 
@@ -9538,48 +10041,69 @@ class CosmeticRuleConverter extends RuleConverterBase {
9538
10041
  * Converts a cosmetic rule to AdGuard syntax, if possible.
9539
10042
  *
9540
10043
  * @param rule Rule node to convert
9541
- * @returns Array of converted rule nodes
10044
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
10045
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
10046
+ * If the rule was not converted, the result array will contain the original node with the same object reference
9542
10047
  * @throws If the rule is invalid or cannot be converted
9543
10048
  */
9544
10049
  static convertToAdg(rule) {
9545
- // Clone the provided AST node to avoid side effects
9546
- const ruleNode = cloneDeep(rule);
9547
- // Convert cosmetic rule modifiers
9548
- if (ruleNode.modifiers) {
9549
- if (ruleNode.syntax === exports.AdblockSyntax.Ubo) {
9550
- // uBO doesn't support this rule:
9551
- // example.com##+js(set-constant.js, foo, bar):matches-path(/baz)
9552
- if (ruleNode.type === exports.CosmeticRuleType.ScriptletInjectionRule) {
9553
- throw new RuleConversionError('uBO scriptlet injection rules don\'t support cosmetic rule modifiers');
9554
- }
9555
- ruleNode.modifiers = AdgCosmeticRuleModifierConverter.convertFromUbo(ruleNode.modifiers);
9556
- }
9557
- else if (ruleNode.syntax === exports.AdblockSyntax.Abp) {
9558
- // TODO: Implement once ABP starts supporting cosmetic rule modifiers
9559
- throw new RuleConversionError('ABP don\'t support cosmetic rule modifiers');
9560
- }
9561
- }
10050
+ let subconverterResult;
9562
10051
  // Convert cosmetic rule based on its type
9563
- switch (ruleNode.type) {
10052
+ switch (rule.type) {
9564
10053
  case exports.CosmeticRuleType.ElementHidingRule:
9565
- return ElementHidingRuleConverter.convertToAdg(ruleNode);
10054
+ subconverterResult = ElementHidingRuleConverter.convertToAdg(rule);
10055
+ break;
9566
10056
  case exports.CosmeticRuleType.ScriptletInjectionRule:
9567
- return ScriptletRuleConverter.convertToAdg(ruleNode);
10057
+ subconverterResult = ScriptletRuleConverter.convertToAdg(rule);
10058
+ break;
9568
10059
  case exports.CosmeticRuleType.CssInjectionRule:
9569
- return CssInjectionRuleConverter.convertToAdg(ruleNode);
10060
+ subconverterResult = CssInjectionRuleConverter.convertToAdg(rule);
10061
+ break;
9570
10062
  case exports.CosmeticRuleType.HtmlFilteringRule:
9571
10063
  // Handle special case: uBO response header filtering rule
9572
- if (ruleNode.body.body.type === exports.CssTreeNodeType.Function
9573
- && ruleNode.body.body.name === UBO_RESPONSEHEADER_MARKER) {
9574
- return HeaderRemovalRuleConverter.convertToAdg(ruleNode);
10064
+ if (rule.body.body.type === exports.CssTreeNodeType.Function
10065
+ && rule.body.body.name === UBO_RESPONSEHEADER_MARKER) {
10066
+ subconverterResult = HeaderRemovalRuleConverter.convertToAdg(rule);
10067
+ }
10068
+ else {
10069
+ subconverterResult = HtmlRuleConverter.convertToAdg(rule);
9575
10070
  }
9576
- return HtmlRuleConverter.convertToAdg(ruleNode);
9577
- // Note: Currently, only ADG supports JS injection rules
10071
+ break;
10072
+ // Note: Currently, only ADG supports JS injection rules, so we don't need to convert them
9578
10073
  case exports.CosmeticRuleType.JsInjectionRule:
9579
- return [ruleNode];
10074
+ subconverterResult = createNodeConversionResult([rule], false);
10075
+ break;
9580
10076
  default:
9581
10077
  throw new RuleConversionError('Unsupported cosmetic rule type');
9582
10078
  }
10079
+ let convertedModifiers;
10080
+ // Convert cosmetic rule modifiers, if any
10081
+ if (rule.modifiers) {
10082
+ if (rule.syntax === exports.AdblockSyntax.Ubo) {
10083
+ // uBO doesn't support this rule:
10084
+ // example.com##+js(set-constant.js, foo, bar):matches-path(/baz)
10085
+ if (rule.type === exports.CosmeticRuleType.ScriptletInjectionRule) {
10086
+ throw new RuleConversionError('uBO scriptlet injection rules don\'t support cosmetic rule modifiers');
10087
+ }
10088
+ convertedModifiers = AdgCosmeticRuleModifierConverter.convertFromUbo(rule.modifiers);
10089
+ }
10090
+ else if (rule.syntax === exports.AdblockSyntax.Abp) {
10091
+ // TODO: Implement once ABP starts supporting cosmetic rule modifiers
10092
+ throw new RuleConversionError('ABP don\'t support cosmetic rule modifiers');
10093
+ }
10094
+ }
10095
+ if ((subconverterResult.result.length > 1 || subconverterResult.isConverted)
10096
+ || (convertedModifiers && convertedModifiers.isConverted)) {
10097
+ // Add modifier list to the subconverter result rules
10098
+ subconverterResult.result.forEach((subconverterRule) => {
10099
+ if (convertedModifiers && subconverterRule.category === exports.RuleCategory.Cosmetic) {
10100
+ // eslint-disable-next-line no-param-reassign
10101
+ subconverterRule.modifiers = convertedModifiers.result;
10102
+ }
10103
+ });
10104
+ return subconverterResult;
10105
+ }
10106
+ return createNodeConversionResult([rule], false);
9583
10107
  }
9584
10108
  }
9585
10109
 
@@ -9651,17 +10175,16 @@ class NetworkRuleModifierListConverter extends ConverterBase {
9651
10175
  * Converts a network rule modifier list to AdGuard format, if possible.
9652
10176
  *
9653
10177
  * @param modifierList Network rule modifier list node to convert
9654
- * @returns Converted modifier list node
10178
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
10179
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
10180
+ * If the node was not converted, the result will contain the original node with the same object reference
9655
10181
  * @throws If the conversion is not possible
9656
10182
  */
9657
10183
  static convertToAdg(modifierList) {
9658
- // Clone the provided AST node to avoid side effects
9659
- const modifierListNode = cloneDeep(modifierList);
9660
- const convertedModifierList = createModifierListNode();
9661
- // We should merge $csp modifiers into one
9662
- const cspValues = [];
9663
- modifierListNode.children.forEach((modifierNode) => {
9664
- // Handle regular modifiers conversion and $csp modifiers collection
10184
+ const conversionMap = new MultiValueMap();
10185
+ // Special case: $csp modifier
10186
+ let cspCount = 0;
10187
+ modifierList.children.forEach((modifierNode, index) => {
9665
10188
  const modifierConversions = ADG_CONVERSION_MAP.get(modifierNode.modifier.value);
9666
10189
  if (modifierConversions) {
9667
10190
  for (const modifierConversion of modifierConversions) {
@@ -9674,17 +10197,14 @@ class NetworkRuleModifierListConverter extends ConverterBase {
9674
10197
  const value = modifierConversion.value
9675
10198
  ? modifierConversion.value(modifierNode.value?.value)
9676
10199
  : modifierNode.value?.value;
9677
- if (name === CSP_MODIFIER && value) {
9678
- // Special case: collect $csp values
9679
- cspValues.push(value);
10200
+ // Check if the name or the value is different from the original modifier
10201
+ // If so, add the converted modifier to the list
10202
+ if (name !== modifierNode.modifier.value || value !== modifierNode.value?.value) {
10203
+ conversionMap.add(index, createModifierNode(name, value, exception));
9680
10204
  }
9681
- else {
9682
- // Regular case: collect the converted modifiers, if the modifier list
9683
- // not already contains the same modifier
9684
- const existingModifier = convertedModifierList.children.find((m) => m.modifier.value === name && m.exception === exception && m.value?.value === value);
9685
- if (!existingModifier) {
9686
- convertedModifierList.children.push(createModifierNode(name, value, exception));
9687
- }
10205
+ // Special case: $csp modifier
10206
+ if (name === CSP_MODIFIER) {
10207
+ cspCount += 1;
9688
10208
  }
9689
10209
  }
9690
10210
  return;
@@ -9707,26 +10227,52 @@ class NetworkRuleModifierListConverter extends ConverterBase {
9707
10227
  // Try to convert the redirect resource name to ADG format
9708
10228
  // This function returns undefined if the resource name is unknown
9709
10229
  const convertedRedirectResource = redirects.convertRedirectNameToAdg(redirectResource);
9710
- convertedModifierList.children.push(createModifierNode(modifierName,
9711
- // If the redirect resource name is unknown, fall back to the original one
9712
- // Later, the validator will throw an error if the resource name is invalid
9713
- convertedRedirectResource || redirectResource, modifierNode.exception));
9714
- return;
9715
- }
9716
- // In all other cases, just copy the modifier as is, if the modifier list
9717
- // not already contains the same modifier
9718
- const existingModifier = convertedModifierList.children.find((m) => m.modifier.value === modifierNode.modifier.value
9719
- && m.exception === modifierNode.exception
9720
- && m.value?.value === modifierNode.value?.value);
9721
- if (!existingModifier) {
9722
- convertedModifierList.children.push(modifierNode);
10230
+ // Check if the modifier name or the redirect resource name is different from the original modifier
10231
+ // If so, add the converted modifier to the list
10232
+ if (modifierName !== modifierNode.modifier.value
10233
+ || (convertedRedirectResource !== undefined && convertedRedirectResource !== redirectResource)) {
10234
+ conversionMap.add(index, createModifierNode(modifierName,
10235
+ // If the redirect resource name is unknown, fall back to the original one
10236
+ // Later, the validator will throw an error if the resource name is invalid
10237
+ convertedRedirectResource || redirectResource, modifierNode.exception));
10238
+ }
9723
10239
  }
9724
10240
  });
9725
- // Merge $csp modifiers into one, then add it to the converted modifier list
9726
- if (cspValues.length > 0) {
9727
- convertedModifierList.children.push(createModifierNode(CSP_MODIFIER, cspValues.join(CSP_SEPARATOR)));
10241
+ // Prepare the result if there are any converted modifiers or $csp modifiers
10242
+ if (conversionMap.size || cspCount) {
10243
+ const modifierListClone = cloneModifierListNode(modifierList);
10244
+ // Replace the original modifiers with the converted ones
10245
+ // One modifier may be replaced with multiple modifiers, so we need to flatten the array
10246
+ modifierListClone.children = modifierListClone.children.map((modifierNode, index) => {
10247
+ const conversionRecord = conversionMap.get(index);
10248
+ if (conversionRecord) {
10249
+ return conversionRecord;
10250
+ }
10251
+ return modifierNode;
10252
+ }).flat();
10253
+ // Special case: $csp modifier: merge multiple $csp modifiers into one
10254
+ // and put it at the end of the modifier list
10255
+ if (cspCount) {
10256
+ const cspValues = [];
10257
+ modifierListClone.children = modifierListClone.children.filter((modifierNode) => {
10258
+ if (modifierNode.modifier.value === CSP_MODIFIER) {
10259
+ if (!modifierNode.value?.value) {
10260
+ throw new RuleConversionError('$csp modifier value is missing');
10261
+ }
10262
+ cspValues.push(modifierNode.value?.value);
10263
+ return false;
10264
+ }
10265
+ return true;
10266
+ });
10267
+ modifierListClone.children.push(createModifierNode(CSP_MODIFIER, cspValues.join(CSP_SEPARATOR)));
10268
+ }
10269
+ // Before returning the result, remove duplicated modifiers
10270
+ modifierListClone.children = modifierListClone.children.filter((modifierNode, index, self) => self.findIndex((m) => m.modifier.value === modifierNode.modifier.value
10271
+ && m.exception === modifierNode.exception
10272
+ && m.value?.value === modifierNode.value?.value) === index);
10273
+ return createConversionResult(modifierListClone, true);
9728
10274
  }
9729
- return convertedModifierList;
10275
+ return createConversionResult(modifierList, false);
9730
10276
  }
9731
10277
  }
9732
10278
 
@@ -9743,17 +10289,35 @@ class NetworkRuleConverter extends RuleConverterBase {
9743
10289
  * Converts a network rule to AdGuard format, if possible.
9744
10290
  *
9745
10291
  * @param rule Rule node to convert
9746
- * @returns Array of converted rule nodes
10292
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
10293
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
10294
+ * If the rule was not converted, the result array will contain the original node with the same object reference
9747
10295
  * @throws If the rule is invalid or cannot be converted
9748
10296
  */
9749
10297
  static convertToAdg(rule) {
9750
- // Clone the provided AST node to avoid side effects
9751
- const ruleNode = cloneDeep(rule);
9752
- // Convert modifiers
9753
- if (ruleNode.modifiers) {
9754
- Object.assign(ruleNode.modifiers, NetworkRuleModifierListConverter.convertToAdg(ruleNode.modifiers));
10298
+ if (rule.modifiers) {
10299
+ const modifiers = NetworkRuleModifierListConverter.convertToAdg(rule.modifiers);
10300
+ // If the object reference is different, it means that the modifiers were converted
10301
+ // In this case, we should clone the entire rule and replace the modifiers with the converted ones
10302
+ if (modifiers.isConverted) {
10303
+ return {
10304
+ result: [{
10305
+ category: exports.RuleCategory.Network,
10306
+ type: 'NetworkRule',
10307
+ syntax: rule.syntax,
10308
+ exception: rule.exception,
10309
+ pattern: {
10310
+ type: 'Value',
10311
+ value: rule.pattern.value,
10312
+ },
10313
+ modifiers: modifiers.result,
10314
+ }],
10315
+ isConverted: true,
10316
+ };
10317
+ }
9755
10318
  }
9756
- return [ruleNode];
10319
+ // If the modifiers were not converted, return the original rule
10320
+ return createNodeConversionResult([rule], false);
9757
10321
  }
9758
10322
  }
9759
10323
 
@@ -9774,48 +10338,27 @@ class RuleConverter extends RuleConverterBase {
9774
10338
  * Converts an adblock filtering rule to AdGuard format, if possible.
9775
10339
  *
9776
10340
  * @param rule Rule node to convert
9777
- * @returns Array of converted rule nodes
10341
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
10342
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
10343
+ * If the rule was not converted, the result array will contain the original node with the same object reference
9778
10344
  * @throws If the rule is invalid or cannot be converted
9779
10345
  */
9780
10346
  static convertToAdg(rule) {
9781
- // Clone the provided AST node to avoid side effects
9782
- const ruleNode = cloneDeep(rule);
9783
10347
  // Delegate conversion to the corresponding sub-converter
9784
10348
  // based on the rule category
9785
- switch (ruleNode.category) {
10349
+ switch (rule.category) {
9786
10350
  case exports.RuleCategory.Comment:
9787
- return CommentRuleConverter.convertToAdg(ruleNode);
10351
+ return CommentRuleConverter.convertToAdg(rule);
9788
10352
  case exports.RuleCategory.Cosmetic:
9789
- return CosmeticRuleConverter.convertToAdg(ruleNode);
10353
+ return CosmeticRuleConverter.convertToAdg(rule);
9790
10354
  case exports.RuleCategory.Network:
9791
- return NetworkRuleConverter.convertToAdg(ruleNode);
10355
+ return NetworkRuleConverter.convertToAdg(rule);
9792
10356
  default:
9793
- throw new RuleConversionError(`Unknown rule category: ${ruleNode.category}`);
10357
+ throw new RuleConversionError(`Unknown rule category: ${rule.category}`);
9794
10358
  }
9795
10359
  }
9796
10360
  }
9797
10361
 
9798
- /**
9799
- * @file Utility functions for working with filter list nodes
9800
- */
9801
- /**
9802
- * Creates a filter list node
9803
- *
9804
- * @param rules Rules to put in the list (optional, defaults to an empty list)
9805
- * @returns Filter list node
9806
- */
9807
- function createFilterListNode(rules = []) {
9808
- const result = {
9809
- type: 'FilterList',
9810
- children: [],
9811
- };
9812
- // We need to clone the rules to avoid side effects
9813
- if (rules.length > 0) {
9814
- result.children = cloneDeep(rules);
9815
- }
9816
- return result;
9817
- }
9818
-
9819
10362
  /**
9820
10363
  * @file Adblock filter list converter
9821
10364
  */
@@ -9834,18 +10377,133 @@ class FilterListConverter extends ConverterBase {
9834
10377
  * Converts an adblock filter list to AdGuard format, if possible.
9835
10378
  *
9836
10379
  * @param filterListNode Filter list node to convert
9837
- * @returns Converted filter list node
9838
- * @throws If the filter list is invalid or cannot be converted
10380
+ * @param tolerant Indicates whether the converter should be tolerant to invalid rules. If enabled and a rule is
10381
+ * invalid, it will be left as is. If disabled and a rule is invalid, the whole filter list will be failed.
10382
+ * Defaults to `true`.
10383
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
10384
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
10385
+ * If the node was not converted, the result will contain the original node with the same object reference
10386
+ * @throws If the filter list is invalid or cannot be converted (if the tolerant mode is disabled)
10387
+ */
10388
+ static convertToAdg(filterListNode, tolerant = true) {
10389
+ // Prepare a map to store the converted rules by their index in the filter list
10390
+ const conversionMap = new MultiValueMap();
10391
+ // Iterate over the filtering rules and convert them one by one, then add them to the result (one conversion may
10392
+ // result in multiple rules)
10393
+ for (let i = 0; i < filterListNode.children.length; i += 1) {
10394
+ try {
10395
+ const convertedRules = RuleConverter.convertToAdg(filterListNode.children[i]);
10396
+ // Add the converted rules to the map if they were converted
10397
+ if (convertedRules.isConverted) {
10398
+ conversionMap.add(i, ...convertedRules.result);
10399
+ }
10400
+ }
10401
+ catch (error) {
10402
+ // If the tolerant mode is disabled, we should throw an error, this will fail the whole filter list
10403
+ // conversion.
10404
+ // Otherwise, we just ignore the error and leave the rule as is
10405
+ if (!tolerant) {
10406
+ throw error;
10407
+ }
10408
+ }
10409
+ }
10410
+ // If the conversion map is empty, it means that no rules were converted, so we can return the original filter
10411
+ // list
10412
+ if (conversionMap.size === 0) {
10413
+ return createConversionResult(filterListNode, false);
10414
+ }
10415
+ // Otherwise, create a new filter list node with the converted rules
10416
+ const convertedFilterList = {
10417
+ type: 'FilterList',
10418
+ children: [],
10419
+ };
10420
+ // Iterate over the original rules again and add them to the converted filter list, replacing the converted
10421
+ // rules with the new ones at the specified indexes
10422
+ for (let i = 0; i < filterListNode.children.length; i += 1) {
10423
+ const rules = conversionMap.get(i);
10424
+ if (rules) {
10425
+ convertedFilterList.children.push(...rules);
10426
+ }
10427
+ else {
10428
+ // We clone the unconverted rules to avoid mutating the original filter list if we return the converted
10429
+ // one
10430
+ convertedFilterList.children.push(clone(filterListNode.children[i]));
10431
+ }
10432
+ }
10433
+ return createConversionResult(convertedFilterList, true);
10434
+ }
10435
+ }
10436
+
10437
+ /**
10438
+ * @file Filter list converter for raw filter lists
10439
+ *
10440
+ * Technically, this is a wrapper around `FilterListConverter` that works with nodes instead of strings.
10441
+ */
10442
+ /**
10443
+ * Adblock filter list converter class.
10444
+ *
10445
+ * You can use this class to convert string-based filter lists, since most of the converters work with nodes.
10446
+ * This class just provides an extra layer on top of the {@link FilterListConverter} and calls the parser/serializer
10447
+ * before/after the conversion internally.
10448
+ *
10449
+ * @todo Implement `convertToUbo` and `convertToAbp`
10450
+ */
10451
+ class RawFilterListConverter extends ConverterBase {
10452
+ /**
10453
+ * Converts an adblock filter list text to AdGuard format, if possible.
10454
+ *
10455
+ * @param rawFilterList Raw filter list text to convert
10456
+ * @param tolerant Indicates whether the converter should be tolerant to invalid rules. If enabled and a rule is
10457
+ * invalid, it will be left as is. If disabled and a rule is invalid, the whole filter list will be failed.
10458
+ * Defaults to `true`.
10459
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
10460
+ * the array of converted filter list text, and its `isConverted` flag indicates whether the original rule was
10461
+ * converted. If the rule was not converted, the original filter list text will be returned
10462
+ * @throws If the filter list is invalid or cannot be converted (if the tolerant mode is disabled)
9839
10463
  */
9840
- static convertToAdg(filterListNode) {
9841
- const result = createFilterListNode();
9842
- // Iterate over the filtering rules and convert them one by one,
9843
- // then add them to the result (one conversion may result in multiple rules)
9844
- for (const ruleNode of filterListNode.children) {
9845
- const convertedRules = RuleConverter.convertToAdg(ruleNode);
9846
- result.children.push(...convertedRules);
10464
+ static convertToAdg(rawFilterList, tolerant = true) {
10465
+ const conversionResult = FilterListConverter.convertToAdg(FilterListParser.parse(rawFilterList, tolerant), tolerant);
10466
+ // If the filter list was not converted, return the original text
10467
+ if (!conversionResult.isConverted) {
10468
+ return createConversionResult(rawFilterList, false);
9847
10469
  }
9848
- return result;
10470
+ // Otherwise, serialize the filter list and return the result
10471
+ return createConversionResult(FilterListParser.generate(conversionResult.result), true);
10472
+ }
10473
+ }
10474
+
10475
+ /**
10476
+ * @file Rule converter for raw rules
10477
+ *
10478
+ * Technically, this is a wrapper around `RuleConverter` that works with nodes instead of strings.
10479
+ */
10480
+ /**
10481
+ * Adblock filtering rule converter class.
10482
+ *
10483
+ * You can use this class to convert string-based adblock rules, since most of the converters work with nodes.
10484
+ * This class just provides an extra layer on top of the {@link RuleConverter} and calls the parser/serializer
10485
+ * before/after the conversion internally.
10486
+ *
10487
+ * @todo Implement `convertToUbo` and `convertToAbp`
10488
+ */
10489
+ class RawRuleConverter extends ConverterBase {
10490
+ /**
10491
+ * Converts an adblock filtering rule to AdGuard format, if possible.
10492
+ *
10493
+ * @param rawRule Raw rule text to convert
10494
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
10495
+ * the array of converted rule texts, and its `isConverted` flag indicates whether the original rule was converted.
10496
+ * If the rule was not converted, the original rule text will be returned
10497
+ * @throws If the rule is invalid or cannot be converted
10498
+ */
10499
+ static convertToAdg(rawRule) {
10500
+ const conversionResult = RuleConverter.convertToAdg(RuleParser.parse(rawRule));
10501
+ // If the rule was not converted, return the original rule text
10502
+ if (!conversionResult.isConverted) {
10503
+ return createConversionResult([rawRule], false);
10504
+ }
10505
+ // Otherwise, serialize the converted rule nodes
10506
+ return createConversionResult(conversionResult.result.map(RuleParser.generate), true);
9849
10507
  }
9850
10508
  }
9851
10509
 
@@ -9927,7 +10585,7 @@ class LogicalExpressionUtils {
9927
10585
  }
9928
10586
  }
9929
10587
 
9930
- const version$1 = "1.1.5";
10588
+ const version$1 = "1.1.7";
9931
10589
 
9932
10590
  /**
9933
10591
  * @file AGTree version
@@ -9988,6 +10646,8 @@ exports.PREPROCESSOR_MARKER = PREPROCESSOR_MARKER;
9988
10646
  exports.ParameterListParser = ParameterListParser;
9989
10647
  exports.PreProcessorCommentRuleParser = PreProcessorCommentRuleParser;
9990
10648
  exports.QuoteUtils = QuoteUtils;
10649
+ exports.RawFilterListConverter = RawFilterListConverter;
10650
+ exports.RawRuleConverter = RawRuleConverter;
9991
10651
  exports.RegExpUtils = RegExpUtils;
9992
10652
  exports.RuleConversionError = RuleConversionError;
9993
10653
  exports.RuleConverter = RuleConverter;