@adguard/agtree 2.0.0-alpha.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /*
2
- * AGTree v2.0.0-alpha.0 (build date: Mon, 29 Jul 2024 13:43:09 GMT)
2
+ * AGTree v2.0.1 (build date: Fri, 06 Sep 2024 12:11:38 GMT)
3
3
  * (c) 2024 Adguard Software Ltd.
4
4
  * Released under the MIT license
5
5
  * https://github.com/AdguardTeam/tsurlfilter/tree/master/packages/agtree#readme
@@ -192,6 +192,7 @@ const NUMBERS = new Set([NUMBER_0, NUMBER_1, NUMBER_2, NUMBER_3, NUMBER_4, NUMBE
192
192
  const REGEX_MARKER = '/';
193
193
  const ADG_SCRIPTLET_MASK = '//scriptlet';
194
194
  const UBO_SCRIPTLET_MASK = '+js';
195
+ const UBO_SCRIPTLET_MASK_LEGACY = 'script:inject';
195
196
  const UBO_HTML_MASK = '^';
196
197
  // Modifiers are separated by ",". For example: "script,domain=example.com"
197
198
  const MODIFIERS_SEPARATOR = ',';
@@ -743,6 +744,15 @@ class StringUtils {
743
744
  }
744
745
  }
745
746
 
747
+ /**
748
+ * Possible operators in the logical expression.
749
+ */
750
+ var OperatorValue;
751
+ (function (OperatorValue) {
752
+ OperatorValue["Not"] = "!";
753
+ OperatorValue["And"] = "&&";
754
+ OperatorValue["Or"] = "||";
755
+ })(OperatorValue || (OperatorValue = {}));
746
756
  /**
747
757
  * Represents the different comment markers that can be used in an adblock rule.
748
758
  *
@@ -1119,6 +1129,15 @@ const isUndefined = value => {
1119
1129
  const isNull = value => {
1120
1130
  return value === null;
1121
1131
  };
1132
+ /**
1133
+ * Checks whether the given value is a string.
1134
+ *
1135
+ * @param value Value to check.
1136
+ * @returns `true` if the value is a string, `false` otherwise.
1137
+ */
1138
+ const isString = value => {
1139
+ return typeof value === 'string';
1140
+ };
1122
1141
  /**
1123
1142
  * Checks whether the given value is an array of Uint8Arrays.
1124
1143
  *
@@ -2938,15 +2957,6 @@ var ParenthesisNodeBinaryPropMap;
2938
2957
  ParenthesisNodeBinaryPropMap[ParenthesisNodeBinaryPropMap["Start"] = 2] = "Start";
2939
2958
  ParenthesisNodeBinaryPropMap[ParenthesisNodeBinaryPropMap["End"] = 3] = "End";
2940
2959
  })(ParenthesisNodeBinaryPropMap || (ParenthesisNodeBinaryPropMap = {}));
2941
- /**
2942
- * Possible operators in the logical expression.
2943
- */
2944
- var OperatorValue;
2945
- (function (OperatorValue) {
2946
- OperatorValue["Not"] = "!";
2947
- OperatorValue["And"] = "&&";
2948
- OperatorValue["Or"] = "||";
2949
- })(OperatorValue || (OperatorValue = {}));
2950
2960
  /**
2951
2961
  * Possible token types in the logical expression.
2952
2962
  */
@@ -2992,7 +3002,9 @@ const getOperatorOrFail = binary => {
2992
3002
  /**
2993
3003
  * Serialization map for known variables.
2994
3004
  */
2995
- const KNOWN_VARIABLES_MAP = new Map([['ext_abp', 0], ['ext_ublock', 1], ['ext_ubol', 2], ['ext_devbuild', 3], ['env_chromium', 4], ['env_edge', 5], ['env_firefox', 6], ['env_mobile', 7], ['env_safari', 8], ['env_mv3', 9], ['false', 10], ['cap_html_filtering', 11], ['cap_user_stylesheet', 12], ['adguard', 13], ['adguard_app_windows', 14], ['adguard_app_mac', 15], ['adguard_app_android', 16], ['adguard_app_ios', 17], ['adguard_ext_safari', 18], ['adguard_ext_chromium', 19], ['adguard_ext_firefox', 20], ['adguard_ext_edge', 21], ['adguard_ext_opera', 22], ['adguard_ext_android_cb', 23]]);
3005
+ const KNOWN_VARIABLES_MAP = new Map([['ext_abp', 0], ['ext_ublock', 1], ['ext_ubol', 2], ['ext_devbuild', 3], ['env_chromium', 4], ['env_edge', 5], ['env_firefox', 6], ['env_mobile', 7], ['env_safari', 8], ['env_mv3', 9], ['false', 10], ['cap_html_filtering', 11], ['cap_user_stylesheet', 12], ['adguard', 13], ['adguard_app_windows', 14], ['adguard_app_mac', 15], ['adguard_app_android', 16], ['adguard_app_ios', 17], ['adguard_ext_safari', 18], ['adguard_ext_chromium', 19], ['adguard_ext_firefox', 20], ['adguard_ext_edge', 21], ['adguard_ext_opera', 22], ['adguard_ext_android_cb', 23]
3006
+ // TODO: Add 'adguard_ext_chromium_mv3' to the list
3007
+ ]);
2996
3008
  /**
2997
3009
  * Deserialization map for known variables.
2998
3010
  */
@@ -5696,7 +5708,7 @@ class CssTokenStream {
5696
5708
  * @returns An array containing the number of tokens skipped and the number of tokens skipped without leading and
5697
5709
  * trailing whitespace tokens.
5698
5710
  */
5699
- skipUntilEx(type, balance) {
5711
+ skipUntilExt(type, balance) {
5700
5712
  let i = this.index;
5701
5713
  let firstNonWsToken = -1; // -1 means no non-whitespace token found yet
5702
5714
  let lastNonWsToken = -1; // -1 means no non-whitespace token found yet
@@ -5924,7 +5936,7 @@ class AdgCssInjectionParser extends ParserBase {
5924
5936
  // └ this one
5925
5937
  const {
5926
5938
  skippedTrimmed: selectorTokensLength
5927
- } = stream.skipUntilEx(TokenType$1.OpenCurlyBracket, balanceShift + 1);
5939
+ } = stream.skipUntilExt(TokenType$1.OpenCurlyBracket, balanceShift + 1);
5928
5940
  stream.expect(TokenType$1.OpenCurlyBracket);
5929
5941
  // If the skipped tokens count is 0 without leading and trailing whitespace characters, then the selector list
5930
5942
  // is empty
@@ -6164,6 +6176,7 @@ class AbpSnippetInjectionBodyParser extends ParserBase {
6164
6176
  *
6165
6177
  * @note Only 256 values can be represented this way.
6166
6178
  */
6179
+ // TODO: Update this map with the actual values
6167
6180
  static FREQUENT_ARGS_SERIALIZATION_MAP = new Map([['abort-current-inline-script', 0], ['abort-on-property-read', 1], ['abort-on-property-write', 2], ['json-prune', 3], ['log', 4], ['prevent-listener', 5], ['cookie-remover', 6], ['override-property-read', 7], ['abort-on-iframe-property-read', 8], ['abort-on-iframe-property-write', 9], ['freeze-element', 10], ['json-override', 11], ['simulate-mouse-event', 12], ['strip-fetch-query-parameter', 13], ['hide-if-contains', 14], ['hide-if-contains-image', 15], ['hide-if-contains-image-hash', 16], ['hide-if-contains-similar-text', 17], ['hide-if-contains-visible-text', 18], ['hide-if-contains-and-matches-style', 19], ['hide-if-graph-matches', 20], ['hide-if-has-and-matches-style', 21], ['hide-if-labelled-by', 22], ['hide-if-matches-xpath', 23], ['hide-if-matches-computed-xpath', 24], ['hide-if-shadow-contains', 25], ['debug', 26], ['trace', 27], ['race', 28]]);
6168
6181
  /**
6169
6182
  * Value map for binary deserialization. This helps to reduce the size of the serialized data,
@@ -6291,6 +6304,7 @@ class UboScriptletInjectionBodyParser extends ParserBase {
6291
6304
  *
6292
6305
  * @note Only 256 values can be represented this way.
6293
6306
  */
6307
+ // TODO: Update this map with the actual values
6294
6308
  static FREQUENT_ARGS_SERIALIZATION_MAP = new Map([['abort-current-script.js', 0], ['acs.js', 1], ['abort-current-inline-script.js', 2], ['acis.js', 3], ['abort-on-property-read.js', 4], ['aopr.js', 5], ['abort-on-property-write.js', 6], ['aopw.js', 7], ['abort-on-stack-trace.js', 8], ['aost.js', 9], ['adjust-setInterval.js', 10], ['nano-setInterval-booster.js', 11], ['nano-sib.js', 12], ['adjust-setTimeout.js', 13], ['nano-setTimeout-booster.js', 14], ['nano-stb.js', 15], ['close-window.js', 16], ['window-close-if.js', 17], ['disable-newtab-links.js', 18], ['evaldata-prune.js', 19], ['json-prune.js', 20], ['addEventListener-logger.js', 21], ['aell.js', 22], ['m3u-prune.js', 23], ['nowebrtc.js', 24], ['addEventListener-defuser.js', 25], ['aeld.js', 26], ['prevent-addEventListener.js', 27], ['adfly-defuser.js', 28], ['noeval-if.js', 29], ['prevent-eval-if.js', 30], ['no-fetch-if.js', 31], ['prevent-fetch.js', 32], ['no-xhr-if.js', 33], ['prevent-xhr.js', 34], ['prevent-refresh.js', 35], ['refresh-defuser.js', 36], ['no-requestAnimationFrame-if.js', 37], ['norafif.js', 38], ['prevent-requestAnimationFrame.js', 39], ['no-setInterval-if.js', 40], ['nosiif.js', 41], ['prevent-setInterval.js', 42], ['setInterval-defuser.js', 43], ['no-setTimeout-if.js', 44], ['nostif.js', 45], ['prevent-setTimeout.js', 46], ['setTimeout-defuser.js', 47], ['no-window-open-if.js', 48], ['nowoif.js', 49], ['prevent-window-open.js', 50], ['window.open-defuser.js', 51], ['remove-attr.js', 52], ['ra.js', 53], ['remove-class.js', 54], ['rc.js', 55], ['remove-cookie.js', 56], ['cookie-remover.js', 57], ['remove-node-text.js', 58], ['rmnt.js', 59], ['set-attr.js', 60], ['set-constant.js', 61], ['set.js', 62], ['set-cookie.js', 63], ['set-local-storage-item.js', 64], ['set-session-storage-item.js', 65], ['xml-prune.js', 66], ['webrtc-if.js', 67], ['overlay-buster.js', 68], ['alert-buster.js', 69], ['golem.de.js', 70], ['href-sanitizer.js', 71], ['call-nothrow.js', 72], ['window.name-defuser.js', 73], ['spoof-css.js', 74], ['trusted-set-constant.js', 75], ['trusted-set.js', 76], ['trusted-set-cookie.js', 77], ['trusted-set-local-storage-item.js', 78], ['trusted-replace-fetch-response.js', 79], ['json-prune-fetch-response.js', 80], ['json-prune-xhr-response.js', 81], ['trusted-replace-xhr-response.js', 82], ['multiup.js', 83], ['prevent-canvas.js', 84], ['set-cookie-reload.js', 85], ['trusted-set-cookie-reload.js', 86], ['trusted-click-element.js', 87], ['trusted-prune-inbound-object.js', 88], ['trusted-prune-outbound-object.js', 89], ['trusted-set-session-storage-item.js', 90], ['trusted-replace-node-text.js', 91], ['trusted-rpnt.js', 92], ['replace-node-text.js', 93], ['rpnt.js', 94]]);
6295
6309
  /**
6296
6310
  * Value map for binary deserialization. This helps to reduce the size of the serialized data,
@@ -6314,11 +6328,17 @@ class UboScriptletInjectionBodyParser extends ParserBase {
6314
6328
  let offset = 0;
6315
6329
  // Skip leading spaces
6316
6330
  offset = StringUtils.skipWS(raw, offset);
6331
+ let scriptletMaskLength = 0;
6332
+ if (raw.startsWith(UBO_SCRIPTLET_MASK, offset)) {
6333
+ scriptletMaskLength = UBO_SCRIPTLET_MASK.length;
6334
+ } else if (raw.startsWith(UBO_SCRIPTLET_MASK_LEGACY, offset)) {
6335
+ scriptletMaskLength = UBO_SCRIPTLET_MASK_LEGACY.length;
6336
+ }
6317
6337
  // Scriptlet call should start with "+js"
6318
- if (!raw.startsWith(UBO_SCRIPTLET_MASK, offset)) {
6338
+ if (!scriptletMaskLength) {
6319
6339
  throw new AdblockSyntaxError(this.ERROR_MESSAGES.NO_SCRIPTLET_MASK, baseOffset + offset, baseOffset + raw.length);
6320
6340
  }
6321
- offset += UBO_SCRIPTLET_MASK.length;
6341
+ offset += scriptletMaskLength;
6322
6342
  // Whitespace is not allowed after the mask
6323
6343
  if (raw[offset] === SPACE) {
6324
6344
  throw new AdblockSyntaxError(this.ERROR_MESSAGES.WHITESPACE_AFTER_MASK, baseOffset + offset, baseOffset + raw.length);
@@ -6363,6 +6383,7 @@ class UboScriptletInjectionBodyParser extends ParserBase {
6363
6383
  if (node.children.length > 1) {
6364
6384
  throw new Error(this.ERROR_MESSAGES.NO_MULTIPLE_SCRIPTLET_CALLS);
6365
6385
  }
6386
+ // During generation, we only support the modern scriptlet mask
6366
6387
  result.push(UBO_SCRIPTLET_MASK);
6367
6388
  result.push(OPEN_PARENTHESIS);
6368
6389
  if (node.children.length > 0) {
@@ -6428,6 +6449,7 @@ class AdgScriptletInjectionBodyParser extends ParserBase {
6428
6449
  *
6429
6450
  * @note Only 256 values can be represented this way.
6430
6451
  */
6452
+ // TODO: Update this map with the actual values
6431
6453
  static FREQUENT_ARGS_SERIALIZATION_MAP = new Map([['abort-current-inline-script', 0], ['abort-on-property-read', 1], ['abort-on-property-write', 2], ['abort-on-stack-trace', 3], ['adjust-setInterval', 4], ['adjust-setTimeout', 5], ['close-window', 6], ['debug-current-inline-script', 7], ['debug-on-property-read', 8], ['debug-on-property-write', 9], ['dir-string', 10], ['disable-newtab-links', 11], ['evaldata-prune', 12], ['json-prune', 13], ['log', 14], ['log-addEventListener', 15], ['log-eval', 16], ['log-on-stack-trace', 17], ['m3u-prune', 18], ['noeval', 19], ['nowebrtc', 20], ['no-topics', 21], ['prevent-addEventListener', 22], ['prevent-adfly', 23], ['prevent-bab', 24], ['prevent-eval-if', 25], ['prevent-fab-3.2.0', 26], ['prevent-fetch', 27], ['prevent-xhr', 28], ['prevent-popads-net', 29], ['prevent-refresh', 30], ['prevent-requestAnimationFrame', 31], ['prevent-setInterval', 32], ['prevent-setTimeout', 33], ['prevent-window-open', 34], ['remove-attr', 35], ['remove-class', 36], ['remove-cookie', 37], ['remove-node-text', 38], ['set-attr', 39], ['set-constant', 40], ['set-cookie', 41], ['set-cookie-reload', 42], ['set-local-storage-item', 43], ['set-popads-dummy', 44], ['set-session-storage-item', 45], ['xml-prune', 46]]);
6432
6454
  /**
6433
6455
  * Value map for binary deserialization. This helps to reduce the size of the serialized data,
@@ -6899,7 +6921,7 @@ class CosmeticRuleParser extends ParserBase {
6899
6921
  };
6900
6922
  };
6901
6923
  const parseUboScriptletInjection = () => {
6902
- if (!rawBody.startsWith(UBO_SCRIPTLET_MASK)) {
6924
+ if (!rawBody.startsWith(UBO_SCRIPTLET_MASK) && !rawBody.startsWith(UBO_SCRIPTLET_MASK_LEGACY)) {
6903
6925
  return null;
6904
6926
  }
6905
6927
  if (!options.parseUboSpecificRules) {
@@ -13854,7 +13876,8 @@ const redirectsCompatibilityTableData = {
13854
13876
  deprecationMessage: null,
13855
13877
  removed: false,
13856
13878
  removalMessage: null,
13857
- isBlocking: false
13879
+ isBlocking: false,
13880
+ resourceTypes: []
13858
13881
  }, {
13859
13882
  name: "1x1.gif",
13860
13883
  aliases: null,
@@ -13866,7 +13889,8 @@ const redirectsCompatibilityTableData = {
13866
13889
  deprecationMessage: null,
13867
13890
  removed: false,
13868
13891
  removalMessage: null,
13869
- isBlocking: false
13892
+ isBlocking: false,
13893
+ resourceTypes: ["image"]
13870
13894
  }, {
13871
13895
  name: "1x1-transparent-gif",
13872
13896
  aliases: null,
@@ -13878,7 +13902,8 @@ const redirectsCompatibilityTableData = {
13878
13902
  deprecationMessage: null,
13879
13903
  removed: false,
13880
13904
  removalMessage: null,
13881
- isBlocking: false
13905
+ isBlocking: false,
13906
+ resourceTypes: []
13882
13907
  }],
13883
13908
  map: {
13884
13909
  "1": 0,
@@ -13909,7 +13934,8 @@ const redirectsCompatibilityTableData = {
13909
13934
  deprecationMessage: null,
13910
13935
  removed: false,
13911
13936
  removalMessage: null,
13912
- isBlocking: false
13937
+ isBlocking: false,
13938
+ resourceTypes: []
13913
13939
  }, {
13914
13940
  name: "2x2.png",
13915
13941
  aliases: null,
@@ -13921,7 +13947,8 @@ const redirectsCompatibilityTableData = {
13921
13947
  deprecationMessage: null,
13922
13948
  removed: false,
13923
13949
  removalMessage: null,
13924
- isBlocking: false
13950
+ isBlocking: false,
13951
+ resourceTypes: ["image"]
13925
13952
  }, {
13926
13953
  name: "2x2-transparent-png",
13927
13954
  aliases: null,
@@ -13933,7 +13960,8 @@ const redirectsCompatibilityTableData = {
13933
13960
  deprecationMessage: null,
13934
13961
  removed: false,
13935
13962
  removalMessage: null,
13936
- isBlocking: false
13963
+ isBlocking: false,
13964
+ resourceTypes: []
13937
13965
  }],
13938
13966
  map: {
13939
13967
  "1": 0,
@@ -13964,7 +13992,8 @@ const redirectsCompatibilityTableData = {
13964
13992
  deprecationMessage: null,
13965
13993
  removed: false,
13966
13994
  removalMessage: null,
13967
- isBlocking: false
13995
+ isBlocking: false,
13996
+ resourceTypes: []
13968
13997
  }, {
13969
13998
  name: "32x32.png",
13970
13999
  aliases: null,
@@ -13976,7 +14005,8 @@ const redirectsCompatibilityTableData = {
13976
14005
  deprecationMessage: null,
13977
14006
  removed: false,
13978
14007
  removalMessage: null,
13979
- isBlocking: false
14008
+ isBlocking: false,
14009
+ resourceTypes: ["image"]
13980
14010
  }, {
13981
14011
  name: "32x32-transparent-png",
13982
14012
  aliases: null,
@@ -13988,7 +14018,8 @@ const redirectsCompatibilityTableData = {
13988
14018
  deprecationMessage: null,
13989
14019
  removed: false,
13990
14020
  removalMessage: null,
13991
- isBlocking: false
14021
+ isBlocking: false,
14022
+ resourceTypes: []
13992
14023
  }],
13993
14024
  map: {
13994
14025
  "1": 0,
@@ -14019,7 +14050,8 @@ const redirectsCompatibilityTableData = {
14019
14050
  deprecationMessage: null,
14020
14051
  removed: false,
14021
14052
  removalMessage: null,
14022
- isBlocking: false
14053
+ isBlocking: false,
14054
+ resourceTypes: []
14023
14055
  }, {
14024
14056
  name: "3x2.png",
14025
14057
  aliases: null,
@@ -14031,7 +14063,8 @@ const redirectsCompatibilityTableData = {
14031
14063
  deprecationMessage: null,
14032
14064
  removed: false,
14033
14065
  removalMessage: null,
14034
- isBlocking: false
14066
+ isBlocking: false,
14067
+ resourceTypes: ["image"]
14035
14068
  }, {
14036
14069
  name: "3x2-transparent-png",
14037
14070
  aliases: null,
@@ -14043,7 +14076,8 @@ const redirectsCompatibilityTableData = {
14043
14076
  deprecationMessage: null,
14044
14077
  removed: false,
14045
14078
  removalMessage: null,
14046
- isBlocking: false
14079
+ isBlocking: false,
14080
+ resourceTypes: []
14047
14081
  }],
14048
14082
  map: {
14049
14083
  "1": 0,
@@ -14074,7 +14108,8 @@ const redirectsCompatibilityTableData = {
14074
14108
  deprecationMessage: null,
14075
14109
  removed: false,
14076
14110
  removalMessage: null,
14077
- isBlocking: false
14111
+ isBlocking: false,
14112
+ resourceTypes: []
14078
14113
  }, {
14079
14114
  name: "amazon_apstag.js",
14080
14115
  aliases: null,
@@ -14086,7 +14121,8 @@ const redirectsCompatibilityTableData = {
14086
14121
  deprecationMessage: null,
14087
14122
  removed: false,
14088
14123
  removalMessage: null,
14089
- isBlocking: false
14124
+ isBlocking: false,
14125
+ resourceTypes: ["script"]
14090
14126
  }],
14091
14127
  map: {
14092
14128
  "1": 0,
@@ -14113,7 +14149,8 @@ const redirectsCompatibilityTableData = {
14113
14149
  deprecationMessage: null,
14114
14150
  removed: false,
14115
14151
  removalMessage: null,
14116
- isBlocking: false
14152
+ isBlocking: false,
14153
+ resourceTypes: ["script"]
14117
14154
  }],
14118
14155
  map: {
14119
14156
  "1024": 0,
@@ -14133,7 +14170,8 @@ const redirectsCompatibilityTableData = {
14133
14170
  deprecationMessage: null,
14134
14171
  removed: false,
14135
14172
  removalMessage: null,
14136
- isBlocking: false
14173
+ isBlocking: false,
14174
+ resourceTypes: ["script"]
14137
14175
  }],
14138
14176
  map: {
14139
14177
  "1024": 0,
@@ -14153,7 +14191,8 @@ const redirectsCompatibilityTableData = {
14153
14191
  deprecationMessage: null,
14154
14192
  removed: false,
14155
14193
  removalMessage: null,
14156
- isBlocking: false
14194
+ isBlocking: false,
14195
+ resourceTypes: []
14157
14196
  }],
14158
14197
  map: {
14159
14198
  "1": 0,
@@ -14176,7 +14215,8 @@ const redirectsCompatibilityTableData = {
14176
14215
  deprecationMessage: null,
14177
14216
  removed: false,
14178
14217
  removalMessage: null,
14179
- isBlocking: false
14218
+ isBlocking: false,
14219
+ resourceTypes: ["script"]
14180
14220
  }],
14181
14221
  map: {
14182
14222
  "1024": 0,
@@ -14196,7 +14236,8 @@ const redirectsCompatibilityTableData = {
14196
14236
  deprecationMessage: null,
14197
14237
  removed: false,
14198
14238
  removalMessage: null,
14199
- isBlocking: true
14239
+ isBlocking: true,
14240
+ resourceTypes: []
14200
14241
  }, {
14201
14242
  name: "click2load.html",
14202
14243
  aliases: null,
@@ -14208,7 +14249,8 @@ const redirectsCompatibilityTableData = {
14208
14249
  deprecationMessage: null,
14209
14250
  removed: false,
14210
14251
  removalMessage: null,
14211
- isBlocking: true
14252
+ isBlocking: true,
14253
+ resourceTypes: []
14212
14254
  }],
14213
14255
  map: {
14214
14256
  "1": 0,
@@ -14235,7 +14277,8 @@ const redirectsCompatibilityTableData = {
14235
14277
  deprecationMessage: null,
14236
14278
  removed: false,
14237
14279
  removalMessage: null,
14238
- isBlocking: false
14280
+ isBlocking: false,
14281
+ resourceTypes: []
14239
14282
  }],
14240
14283
  map: {
14241
14284
  "1": 0,
@@ -14258,7 +14301,8 @@ const redirectsCompatibilityTableData = {
14258
14301
  deprecationMessage: null,
14259
14302
  removed: false,
14260
14303
  removalMessage: null,
14261
- isBlocking: false
14304
+ isBlocking: false,
14305
+ resourceTypes: []
14262
14306
  }, {
14263
14307
  name: "empty",
14264
14308
  aliases: null,
@@ -14270,7 +14314,8 @@ const redirectsCompatibilityTableData = {
14270
14314
  deprecationMessage: null,
14271
14315
  removed: false,
14272
14316
  removalMessage: null,
14273
- isBlocking: false
14317
+ isBlocking: false,
14318
+ resourceTypes: []
14274
14319
  }],
14275
14320
  map: {
14276
14321
  "1": 0,
@@ -14297,7 +14342,8 @@ const redirectsCompatibilityTableData = {
14297
14342
  deprecationMessage: null,
14298
14343
  removed: false,
14299
14344
  removalMessage: null,
14300
- isBlocking: false
14345
+ isBlocking: false,
14346
+ resourceTypes: []
14301
14347
  }, {
14302
14348
  name: "fingerprint2.js",
14303
14349
  aliases: null,
@@ -14309,7 +14355,8 @@ const redirectsCompatibilityTableData = {
14309
14355
  deprecationMessage: null,
14310
14356
  removed: false,
14311
14357
  removalMessage: null,
14312
- isBlocking: false
14358
+ isBlocking: false,
14359
+ resourceTypes: ["script"]
14313
14360
  }],
14314
14361
  map: {
14315
14362
  "1": 0,
@@ -14336,7 +14383,8 @@ const redirectsCompatibilityTableData = {
14336
14383
  deprecationMessage: null,
14337
14384
  removed: false,
14338
14385
  removalMessage: null,
14339
- isBlocking: false
14386
+ isBlocking: false,
14387
+ resourceTypes: []
14340
14388
  }, {
14341
14389
  name: "fingerprint3.js",
14342
14390
  aliases: null,
@@ -14348,7 +14396,8 @@ const redirectsCompatibilityTableData = {
14348
14396
  deprecationMessage: null,
14349
14397
  removed: false,
14350
14398
  removalMessage: null,
14351
- isBlocking: false
14399
+ isBlocking: false,
14400
+ resourceTypes: ["script"]
14352
14401
  }],
14353
14402
  map: {
14354
14403
  "1": 0,
@@ -14375,7 +14424,8 @@ const redirectsCompatibilityTableData = {
14375
14424
  deprecationMessage: null,
14376
14425
  removed: false,
14377
14426
  removalMessage: null,
14378
- isBlocking: false
14427
+ isBlocking: false,
14428
+ resourceTypes: []
14379
14429
  }],
14380
14430
  map: {
14381
14431
  "1": 0,
@@ -14398,7 +14448,8 @@ const redirectsCompatibilityTableData = {
14398
14448
  deprecationMessage: null,
14399
14449
  removed: false,
14400
14450
  removalMessage: null,
14401
- isBlocking: false
14451
+ isBlocking: false,
14452
+ resourceTypes: []
14402
14453
  }, {
14403
14454
  name: "google-analytics_ga.js",
14404
14455
  aliases: null,
@@ -14410,7 +14461,8 @@ const redirectsCompatibilityTableData = {
14410
14461
  deprecationMessage: null,
14411
14462
  removed: false,
14412
14463
  removalMessage: null,
14413
- isBlocking: false
14464
+ isBlocking: false,
14465
+ resourceTypes: ["script"]
14414
14466
  }],
14415
14467
  map: {
14416
14468
  "1": 0,
@@ -14437,7 +14489,8 @@ const redirectsCompatibilityTableData = {
14437
14489
  deprecationMessage: null,
14438
14490
  removed: false,
14439
14491
  removalMessage: null,
14440
- isBlocking: false
14492
+ isBlocking: false,
14493
+ resourceTypes: []
14441
14494
  }, {
14442
14495
  name: "google-analytics_analytics.js",
14443
14496
  aliases: null,
@@ -14449,7 +14502,8 @@ const redirectsCompatibilityTableData = {
14449
14502
  deprecationMessage: null,
14450
14503
  removed: false,
14451
14504
  removalMessage: null,
14452
- isBlocking: false
14505
+ isBlocking: false,
14506
+ resourceTypes: ["script"]
14453
14507
  }],
14454
14508
  map: {
14455
14509
  "1": 0,
@@ -14476,7 +14530,8 @@ const redirectsCompatibilityTableData = {
14476
14530
  deprecationMessage: null,
14477
14531
  removed: false,
14478
14532
  removalMessage: null,
14479
- isBlocking: false
14533
+ isBlocking: false,
14534
+ resourceTypes: ["script"]
14480
14535
  }],
14481
14536
  map: {
14482
14537
  "1024": 0,
@@ -14496,7 +14551,8 @@ const redirectsCompatibilityTableData = {
14496
14551
  deprecationMessage: null,
14497
14552
  removed: false,
14498
14553
  removalMessage: null,
14499
- isBlocking: false
14554
+ isBlocking: false,
14555
+ resourceTypes: ["script"]
14500
14556
  }],
14501
14557
  map: {
14502
14558
  "1024": 0,
@@ -14516,7 +14572,8 @@ const redirectsCompatibilityTableData = {
14516
14572
  deprecationMessage: null,
14517
14573
  removed: false,
14518
14574
  removalMessage: null,
14519
- isBlocking: false
14575
+ isBlocking: false,
14576
+ resourceTypes: []
14520
14577
  }, {
14521
14578
  name: "google-ima.js",
14522
14579
  aliases: null,
@@ -14528,7 +14585,8 @@ const redirectsCompatibilityTableData = {
14528
14585
  deprecationMessage: null,
14529
14586
  removed: false,
14530
14587
  removalMessage: null,
14531
- isBlocking: false
14588
+ isBlocking: false,
14589
+ resourceTypes: ["script"]
14532
14590
  }],
14533
14591
  map: {
14534
14592
  "1": 0,
@@ -14546,7 +14604,7 @@ const redirectsCompatibilityTableData = {
14546
14604
  }, {
14547
14605
  shared: [{
14548
14606
  name: "googlesyndication-adsbygoogle",
14549
- aliases: ["ubo-googlesyndication_adsbygoogle.js", "googlesyndication_adsbygoogle.js"],
14607
+ aliases: ["ubo-googlesyndication_adsbygoogle.js", "ubo-googlesyndication.com/adsbygoogle.js", "googlesyndication_adsbygoogle.js"],
14550
14608
  description: "Mocks Google AdSense API.",
14551
14609
  docs: null,
14552
14610
  versionAdded: null,
@@ -14555,10 +14613,11 @@ const redirectsCompatibilityTableData = {
14555
14613
  deprecationMessage: null,
14556
14614
  removed: false,
14557
14615
  removalMessage: null,
14558
- isBlocking: false
14616
+ isBlocking: false,
14617
+ resourceTypes: []
14559
14618
  }, {
14560
14619
  name: "googlesyndication_adsbygoogle.js",
14561
- aliases: null,
14620
+ aliases: ["googlesyndication.com/adsbygoogle.js"],
14562
14621
  description: "Mocks Google AdSense API.",
14563
14622
  docs: null,
14564
14623
  versionAdded: null,
@@ -14567,7 +14626,8 @@ const redirectsCompatibilityTableData = {
14567
14626
  deprecationMessage: null,
14568
14627
  removed: false,
14569
14628
  removalMessage: null,
14570
- isBlocking: false
14629
+ isBlocking: false,
14630
+ resourceTypes: ["script"]
14571
14631
  }],
14572
14632
  map: {
14573
14633
  "1": 0,
@@ -14594,7 +14654,8 @@ const redirectsCompatibilityTableData = {
14594
14654
  deprecationMessage: null,
14595
14655
  removed: false,
14596
14656
  removalMessage: null,
14597
- isBlocking: false
14657
+ isBlocking: false,
14658
+ resourceTypes: []
14598
14659
  }, {
14599
14660
  name: "googletagservices_gpt.js",
14600
14661
  aliases: null,
@@ -14606,7 +14667,8 @@ const redirectsCompatibilityTableData = {
14606
14667
  deprecationMessage: null,
14607
14668
  removed: false,
14608
14669
  removalMessage: null,
14609
- isBlocking: false
14670
+ isBlocking: false,
14671
+ resourceTypes: ["script"]
14610
14672
  }],
14611
14673
  map: {
14612
14674
  "1": 0,
@@ -14633,7 +14695,8 @@ const redirectsCompatibilityTableData = {
14633
14695
  deprecationMessage: null,
14634
14696
  removed: false,
14635
14697
  removalMessage: null,
14636
- isBlocking: false
14698
+ isBlocking: false,
14699
+ resourceTypes: ["script"]
14637
14700
  }],
14638
14701
  map: {
14639
14702
  "1024": 0,
@@ -14653,7 +14716,8 @@ const redirectsCompatibilityTableData = {
14653
14716
  deprecationMessage: null,
14654
14717
  removed: false,
14655
14718
  removalMessage: null,
14656
- isBlocking: false
14719
+ isBlocking: false,
14720
+ resourceTypes: []
14657
14721
  }],
14658
14722
  map: {
14659
14723
  "1": 0,
@@ -14676,7 +14740,8 @@ const redirectsCompatibilityTableData = {
14676
14740
  deprecationMessage: null,
14677
14741
  removed: false,
14678
14742
  removalMessage: null,
14679
- isBlocking: false
14743
+ isBlocking: false,
14744
+ resourceTypes: []
14680
14745
  }],
14681
14746
  map: {
14682
14747
  "1": 0,
@@ -14699,7 +14764,8 @@ const redirectsCompatibilityTableData = {
14699
14764
  deprecationMessage: null,
14700
14765
  removed: false,
14701
14766
  removalMessage: null,
14702
- isBlocking: false
14767
+ isBlocking: false,
14768
+ resourceTypes: []
14703
14769
  }],
14704
14770
  map: {
14705
14771
  "1": 0,
@@ -14722,7 +14788,8 @@ const redirectsCompatibilityTableData = {
14722
14788
  deprecationMessage: null,
14723
14789
  removed: false,
14724
14790
  removalMessage: null,
14725
- isBlocking: false
14791
+ isBlocking: false,
14792
+ resourceTypes: []
14726
14793
  }],
14727
14794
  map: {
14728
14795
  "1": 0,
@@ -14745,7 +14812,8 @@ const redirectsCompatibilityTableData = {
14745
14812
  deprecationMessage: null,
14746
14813
  removed: false,
14747
14814
  removalMessage: null,
14748
- isBlocking: false
14815
+ isBlocking: false,
14816
+ resourceTypes: []
14749
14817
  }],
14750
14818
  map: {
14751
14819
  "1": 0,
@@ -14768,7 +14836,8 @@ const redirectsCompatibilityTableData = {
14768
14836
  deprecationMessage: null,
14769
14837
  removed: false,
14770
14838
  removalMessage: null,
14771
- isBlocking: false
14839
+ isBlocking: false,
14840
+ resourceTypes: []
14772
14841
  }],
14773
14842
  map: {
14774
14843
  "1": 0,
@@ -14791,7 +14860,8 @@ const redirectsCompatibilityTableData = {
14791
14860
  deprecationMessage: null,
14792
14861
  removed: false,
14793
14862
  removalMessage: null,
14794
- isBlocking: false
14863
+ isBlocking: false,
14864
+ resourceTypes: []
14795
14865
  }, {
14796
14866
  name: "noeval.js",
14797
14867
  aliases: null,
@@ -14803,7 +14873,8 @@ const redirectsCompatibilityTableData = {
14803
14873
  deprecationMessage: null,
14804
14874
  removed: false,
14805
14875
  removalMessage: null,
14806
- isBlocking: false
14876
+ isBlocking: false,
14877
+ resourceTypes: ["script"]
14807
14878
  }],
14808
14879
  map: {
14809
14880
  "1": 0,
@@ -14830,7 +14901,8 @@ const redirectsCompatibilityTableData = {
14830
14901
  deprecationMessage: null,
14831
14902
  removed: false,
14832
14903
  removalMessage: null,
14833
- isBlocking: false
14904
+ isBlocking: false,
14905
+ resourceTypes: ["media"]
14834
14906
  }],
14835
14907
  map: {
14836
14908
  "1024": 0,
@@ -14850,7 +14922,8 @@ const redirectsCompatibilityTableData = {
14850
14922
  deprecationMessage: null,
14851
14923
  removed: false,
14852
14924
  removalMessage: null,
14853
- isBlocking: false
14925
+ isBlocking: false,
14926
+ resourceTypes: []
14854
14927
  }, {
14855
14928
  name: "noop.css",
14856
14929
  aliases: null,
@@ -14862,7 +14935,8 @@ const redirectsCompatibilityTableData = {
14862
14935
  deprecationMessage: null,
14863
14936
  removed: false,
14864
14937
  removalMessage: null,
14865
- isBlocking: false
14938
+ isBlocking: false,
14939
+ resourceTypes: ["stylesheet"]
14866
14940
  }, {
14867
14941
  name: "blank-css",
14868
14942
  aliases: null,
@@ -14874,7 +14948,8 @@ const redirectsCompatibilityTableData = {
14874
14948
  deprecationMessage: null,
14875
14949
  removed: false,
14876
14950
  removalMessage: null,
14877
- isBlocking: false
14951
+ isBlocking: false,
14952
+ resourceTypes: []
14878
14953
  }],
14879
14954
  map: {
14880
14955
  "1": 0,
@@ -14905,7 +14980,8 @@ const redirectsCompatibilityTableData = {
14905
14980
  deprecationMessage: null,
14906
14981
  removed: false,
14907
14982
  removalMessage: null,
14908
- isBlocking: false
14983
+ isBlocking: false,
14984
+ resourceTypes: []
14909
14985
  }, {
14910
14986
  name: "noop.html",
14911
14987
  aliases: null,
@@ -14917,7 +14993,8 @@ const redirectsCompatibilityTableData = {
14917
14993
  deprecationMessage: null,
14918
14994
  removed: false,
14919
14995
  removalMessage: null,
14920
- isBlocking: false
14996
+ isBlocking: false,
14997
+ resourceTypes: ["sub_frame"]
14921
14998
  }, {
14922
14999
  name: "blank-html",
14923
15000
  aliases: null,
@@ -14929,7 +15006,8 @@ const redirectsCompatibilityTableData = {
14929
15006
  deprecationMessage: null,
14930
15007
  removed: false,
14931
15008
  removalMessage: null,
14932
- isBlocking: false
15009
+ isBlocking: false,
15010
+ resourceTypes: []
14933
15011
  }],
14934
15012
  map: {
14935
15013
  "1": 0,
@@ -14960,7 +15038,8 @@ const redirectsCompatibilityTableData = {
14960
15038
  deprecationMessage: null,
14961
15039
  removed: false,
14962
15040
  removalMessage: null,
14963
- isBlocking: false
15041
+ isBlocking: false,
15042
+ resourceTypes: []
14964
15043
  }, {
14965
15044
  name: "noop.js",
14966
15045
  aliases: null,
@@ -14972,7 +15051,8 @@ const redirectsCompatibilityTableData = {
14972
15051
  deprecationMessage: null,
14973
15052
  removed: false,
14974
15053
  removalMessage: null,
14975
- isBlocking: false
15054
+ isBlocking: false,
15055
+ resourceTypes: ["script"]
14976
15056
  }, {
14977
15057
  name: "blank-js",
14978
15058
  aliases: null,
@@ -14984,7 +15064,8 @@ const redirectsCompatibilityTableData = {
14984
15064
  deprecationMessage: null,
14985
15065
  removed: false,
14986
15066
  removalMessage: null,
14987
- isBlocking: false
15067
+ isBlocking: false,
15068
+ resourceTypes: []
14988
15069
  }],
14989
15070
  map: {
14990
15071
  "1": 0,
@@ -15015,7 +15096,8 @@ const redirectsCompatibilityTableData = {
15015
15096
  deprecationMessage: null,
15016
15097
  removed: false,
15017
15098
  removalMessage: null,
15018
- isBlocking: false
15099
+ isBlocking: false,
15100
+ resourceTypes: []
15019
15101
  }, {
15020
15102
  name: "noop.json",
15021
15103
  aliases: null,
@@ -15027,7 +15109,8 @@ const redirectsCompatibilityTableData = {
15027
15109
  deprecationMessage: null,
15028
15110
  removed: false,
15029
15111
  removalMessage: null,
15030
- isBlocking: false
15112
+ isBlocking: false,
15113
+ resourceTypes: []
15031
15114
  }],
15032
15115
  map: {
15033
15116
  "1": 0,
@@ -15054,7 +15137,8 @@ const redirectsCompatibilityTableData = {
15054
15137
  deprecationMessage: null,
15055
15138
  removed: false,
15056
15139
  removalMessage: null,
15057
- isBlocking: false
15140
+ isBlocking: false,
15141
+ resourceTypes: []
15058
15142
  }, {
15059
15143
  name: "noop-0.1s.mp3",
15060
15144
  aliases: null,
@@ -15066,7 +15150,8 @@ const redirectsCompatibilityTableData = {
15066
15150
  deprecationMessage: null,
15067
15151
  removed: false,
15068
15152
  removalMessage: null,
15069
- isBlocking: false
15153
+ isBlocking: false,
15154
+ resourceTypes: ["media"]
15070
15155
  }, {
15071
15156
  name: "blank-mp3",
15072
15157
  aliases: null,
@@ -15078,7 +15163,8 @@ const redirectsCompatibilityTableData = {
15078
15163
  deprecationMessage: null,
15079
15164
  removed: false,
15080
15165
  removalMessage: null,
15081
- isBlocking: false
15166
+ isBlocking: false,
15167
+ resourceTypes: []
15082
15168
  }],
15083
15169
  map: {
15084
15170
  "1": 0,
@@ -15109,7 +15195,8 @@ const redirectsCompatibilityTableData = {
15109
15195
  deprecationMessage: null,
15110
15196
  removed: false,
15111
15197
  removalMessage: null,
15112
- isBlocking: false
15198
+ isBlocking: false,
15199
+ resourceTypes: []
15113
15200
  }, {
15114
15201
  name: "noop-1s.mp4",
15115
15202
  aliases: null,
@@ -15121,7 +15208,8 @@ const redirectsCompatibilityTableData = {
15121
15208
  deprecationMessage: null,
15122
15209
  removed: false,
15123
15210
  removalMessage: null,
15124
- isBlocking: false
15211
+ isBlocking: false,
15212
+ resourceTypes: ["media"]
15125
15213
  }, {
15126
15214
  name: "blank-mp4",
15127
15215
  aliases: null,
@@ -15133,7 +15221,8 @@ const redirectsCompatibilityTableData = {
15133
15221
  deprecationMessage: null,
15134
15222
  removed: false,
15135
15223
  removalMessage: null,
15136
- isBlocking: false
15224
+ isBlocking: false,
15225
+ resourceTypes: []
15137
15226
  }],
15138
15227
  map: {
15139
15228
  "1": 0,
@@ -15164,7 +15253,8 @@ const redirectsCompatibilityTableData = {
15164
15253
  deprecationMessage: null,
15165
15254
  removed: false,
15166
15255
  removalMessage: null,
15167
- isBlocking: false
15256
+ isBlocking: false,
15257
+ resourceTypes: []
15168
15258
  }, {
15169
15259
  name: "noop.txt",
15170
15260
  aliases: null,
@@ -15176,7 +15266,8 @@ const redirectsCompatibilityTableData = {
15176
15266
  deprecationMessage: null,
15177
15267
  removed: false,
15178
15268
  removalMessage: null,
15179
- isBlocking: false
15269
+ isBlocking: false,
15270
+ resourceTypes: ["image", "media", "sub_frame", "stylesheet", "script", "xmlhttprequest", "other"]
15180
15271
  }, {
15181
15272
  name: "blank-text",
15182
15273
  aliases: null,
@@ -15188,7 +15279,8 @@ const redirectsCompatibilityTableData = {
15188
15279
  deprecationMessage: null,
15189
15280
  removed: false,
15190
15281
  removalMessage: null,
15191
- isBlocking: false
15282
+ isBlocking: false,
15283
+ resourceTypes: []
15192
15284
  }],
15193
15285
  map: {
15194
15286
  "1": 0,
@@ -15219,7 +15311,8 @@ const redirectsCompatibilityTableData = {
15219
15311
  deprecationMessage: null,
15220
15312
  removed: false,
15221
15313
  removalMessage: null,
15222
- isBlocking: false
15314
+ isBlocking: false,
15315
+ resourceTypes: []
15223
15316
  }],
15224
15317
  map: {
15225
15318
  "1": 0,
@@ -15242,7 +15335,8 @@ const redirectsCompatibilityTableData = {
15242
15335
  deprecationMessage: null,
15243
15336
  removed: false,
15244
15337
  removalMessage: null,
15245
- isBlocking: false
15338
+ isBlocking: false,
15339
+ resourceTypes: []
15246
15340
  }],
15247
15341
  map: {
15248
15342
  "1": 0,
@@ -15265,7 +15359,8 @@ const redirectsCompatibilityTableData = {
15265
15359
  deprecationMessage: null,
15266
15360
  removed: false,
15267
15361
  removalMessage: null,
15268
- isBlocking: false
15362
+ isBlocking: false,
15363
+ resourceTypes: []
15269
15364
  }],
15270
15365
  map: {
15271
15366
  "1": 0,
@@ -15288,7 +15383,8 @@ const redirectsCompatibilityTableData = {
15288
15383
  deprecationMessage: null,
15289
15384
  removed: false,
15290
15385
  removalMessage: null,
15291
- isBlocking: false
15386
+ isBlocking: false,
15387
+ resourceTypes: []
15292
15388
  }, {
15293
15389
  name: "noop-vmap1.0.xml",
15294
15390
  aliases: null,
@@ -15300,7 +15396,8 @@ const redirectsCompatibilityTableData = {
15300
15396
  deprecationMessage: null,
15301
15397
  removed: false,
15302
15398
  removalMessage: null,
15303
- isBlocking: false
15399
+ isBlocking: false,
15400
+ resourceTypes: ["media"]
15304
15401
  }],
15305
15402
  map: {
15306
15403
  "1": 0,
@@ -15327,7 +15424,8 @@ const redirectsCompatibilityTableData = {
15327
15424
  deprecationMessage: null,
15328
15425
  removed: false,
15329
15426
  removalMessage: null,
15330
- isBlocking: false
15427
+ isBlocking: false,
15428
+ resourceTypes: []
15331
15429
  }, {
15332
15430
  name: "nowebrtc.js",
15333
15431
  aliases: null,
@@ -15339,7 +15437,8 @@ const redirectsCompatibilityTableData = {
15339
15437
  deprecationMessage: null,
15340
15438
  removed: false,
15341
15439
  removalMessage: null,
15342
- isBlocking: false
15440
+ isBlocking: false,
15441
+ resourceTypes: ["other"]
15343
15442
  }],
15344
15443
  map: {
15345
15444
  "1": 0,
@@ -15366,7 +15465,8 @@ const redirectsCompatibilityTableData = {
15366
15465
  deprecationMessage: null,
15367
15466
  removed: false,
15368
15467
  removalMessage: null,
15369
- isBlocking: false
15468
+ isBlocking: false,
15469
+ resourceTypes: ["script"]
15370
15470
  }],
15371
15471
  map: {
15372
15472
  "1024": 0,
@@ -15386,7 +15486,8 @@ const redirectsCompatibilityTableData = {
15386
15486
  deprecationMessage: null,
15387
15487
  removed: false,
15388
15488
  removalMessage: null,
15389
- isBlocking: false
15489
+ isBlocking: false,
15490
+ resourceTypes: []
15390
15491
  }],
15391
15492
  map: {
15392
15493
  "1": 0,
@@ -15409,7 +15510,8 @@ const redirectsCompatibilityTableData = {
15409
15510
  deprecationMessage: null,
15410
15511
  removed: false,
15411
15512
  removalMessage: null,
15412
- isBlocking: false
15513
+ isBlocking: false,
15514
+ resourceTypes: []
15413
15515
  }, {
15414
15516
  name: "prebid-ads.js",
15415
15517
  aliases: null,
@@ -15421,7 +15523,8 @@ const redirectsCompatibilityTableData = {
15421
15523
  deprecationMessage: null,
15422
15524
  removed: false,
15423
15525
  removalMessage: null,
15424
- isBlocking: false
15526
+ isBlocking: false,
15527
+ resourceTypes: ["script"]
15425
15528
  }],
15426
15529
  map: {
15427
15530
  "1": 0,
@@ -15448,7 +15551,8 @@ const redirectsCompatibilityTableData = {
15448
15551
  deprecationMessage: null,
15449
15552
  removed: false,
15450
15553
  removalMessage: null,
15451
- isBlocking: false
15554
+ isBlocking: false,
15555
+ resourceTypes: []
15452
15556
  }],
15453
15557
  map: {
15454
15558
  "1": 0,
@@ -15471,7 +15575,8 @@ const redirectsCompatibilityTableData = {
15471
15575
  deprecationMessage: null,
15472
15576
  removed: false,
15473
15577
  removalMessage: null,
15474
- isBlocking: false
15578
+ isBlocking: false,
15579
+ resourceTypes: []
15475
15580
  }, {
15476
15581
  name: "nobab.js",
15477
15582
  aliases: null,
@@ -15483,7 +15588,8 @@ const redirectsCompatibilityTableData = {
15483
15588
  deprecationMessage: null,
15484
15589
  removed: false,
15485
15590
  removalMessage: null,
15486
- isBlocking: false
15591
+ isBlocking: false,
15592
+ resourceTypes: ["script"]
15487
15593
  }],
15488
15594
  map: {
15489
15595
  "1": 0,
@@ -15510,7 +15616,8 @@ const redirectsCompatibilityTableData = {
15510
15616
  deprecationMessage: null,
15511
15617
  removed: false,
15512
15618
  removalMessage: null,
15513
- isBlocking: false
15619
+ isBlocking: false,
15620
+ resourceTypes: []
15514
15621
  }, {
15515
15622
  name: "nobab2.js",
15516
15623
  aliases: null,
@@ -15522,7 +15629,8 @@ const redirectsCompatibilityTableData = {
15522
15629
  deprecationMessage: null,
15523
15630
  removed: false,
15524
15631
  removalMessage: null,
15525
- isBlocking: false
15632
+ isBlocking: false,
15633
+ resourceTypes: ["script"]
15526
15634
  }],
15527
15635
  map: {
15528
15636
  "1": 0,
@@ -15549,10 +15657,11 @@ const redirectsCompatibilityTableData = {
15549
15657
  deprecationMessage: null,
15550
15658
  removed: false,
15551
15659
  removalMessage: null,
15552
- isBlocking: false
15660
+ isBlocking: false,
15661
+ resourceTypes: []
15553
15662
  }, {
15554
15663
  name: "nofab.js",
15555
- aliases: null,
15664
+ aliases: ["fuckadblock.js-3.2.0", "fuckadblock.js-3.2.0.js"],
15556
15665
  description: "Mocks FAB script v3.2.0.",
15557
15666
  docs: null,
15558
15667
  versionAdded: null,
@@ -15561,7 +15670,8 @@ const redirectsCompatibilityTableData = {
15561
15670
  deprecationMessage: null,
15562
15671
  removed: false,
15563
15672
  removalMessage: null,
15564
- isBlocking: false
15673
+ isBlocking: false,
15674
+ resourceTypes: ["script"]
15565
15675
  }],
15566
15676
  map: {
15567
15677
  "1": 0,
@@ -15588,7 +15698,8 @@ const redirectsCompatibilityTableData = {
15588
15698
  deprecationMessage: null,
15589
15699
  removed: false,
15590
15700
  removalMessage: null,
15591
- isBlocking: false
15701
+ isBlocking: false,
15702
+ resourceTypes: []
15592
15703
  }, {
15593
15704
  name: "popads.js",
15594
15705
  aliases: null,
@@ -15600,7 +15711,8 @@ const redirectsCompatibilityTableData = {
15600
15711
  deprecationMessage: null,
15601
15712
  removed: false,
15602
15713
  removalMessage: null,
15603
- isBlocking: false
15714
+ isBlocking: false,
15715
+ resourceTypes: ["script"]
15604
15716
  }],
15605
15717
  map: {
15606
15718
  "1": 0,
@@ -15627,7 +15739,8 @@ const redirectsCompatibilityTableData = {
15627
15739
  deprecationMessage: null,
15628
15740
  removed: false,
15629
15741
  removalMessage: null,
15630
- isBlocking: false
15742
+ isBlocking: false,
15743
+ resourceTypes: []
15631
15744
  }, {
15632
15745
  name: "scorecardresearch_beacon.js",
15633
15746
  aliases: null,
@@ -15639,7 +15752,8 @@ const redirectsCompatibilityTableData = {
15639
15752
  deprecationMessage: null,
15640
15753
  removed: false,
15641
15754
  removalMessage: null,
15642
- isBlocking: false
15755
+ isBlocking: false,
15756
+ resourceTypes: ["script"]
15643
15757
  }],
15644
15758
  map: {
15645
15759
  "1": 0,
@@ -15666,7 +15780,8 @@ const redirectsCompatibilityTableData = {
15666
15780
  deprecationMessage: null,
15667
15781
  removed: false,
15668
15782
  removalMessage: null,
15669
- isBlocking: false
15783
+ isBlocking: false,
15784
+ resourceTypes: []
15670
15785
  }, {
15671
15786
  name: "popads-dummy.js",
15672
15787
  aliases: null,
@@ -15678,7 +15793,8 @@ const redirectsCompatibilityTableData = {
15678
15793
  deprecationMessage: null,
15679
15794
  removed: false,
15680
15795
  removalMessage: null,
15681
- isBlocking: false
15796
+ isBlocking: false,
15797
+ resourceTypes: ["script"]
15682
15798
  }],
15683
15799
  map: {
15684
15800
  "1": 0,
@@ -15739,7 +15855,9 @@ const redirectsCompatibilityTableData = {
15739
15855
  "google-ima.js": 19,
15740
15856
  "googlesyndication-adsbygoogle": 20,
15741
15857
  "ubo-googlesyndication_adsbygoogle.js": 20,
15858
+ "ubo-googlesyndication.com/adsbygoogle.js": 20,
15742
15859
  "googlesyndication_adsbygoogle.js": 20,
15860
+ "googlesyndication.com/adsbygoogle.js": 20,
15743
15861
  "googletagservices-gpt": 21,
15744
15862
  "ubo-googletagservices_gpt.js": 21,
15745
15863
  "googletagservices_gpt.js": 21,
@@ -15798,6 +15916,8 @@ const redirectsCompatibilityTableData = {
15798
15916
  "nobab2.js": 48,
15799
15917
  "prevent-fab-3.2.0": 49,
15800
15918
  "nofab.js": 49,
15919
+ "fuckadblock.js-3.2.0": 49,
15920
+ "fuckadblock.js-3.2.0.js": 49,
15801
15921
  "prevent-popads-net": 50,
15802
15922
  "popads.js": 50,
15803
15923
  "scorecardresearch-beacon": 51,
@@ -18505,7 +18625,7 @@ const scriptletsCompatibilityTableData = {
18505
18625
  }]
18506
18626
  }, {
18507
18627
  name: "remove-attr.js",
18508
- aliases: ["ra.js"],
18628
+ aliases: ["ra.js", "ra", "remove-attr"],
18509
18629
  description: null,
18510
18630
  docs: "https://github.com/gorhill/uBlock/wiki/Resources-Library#remove-attrjs-",
18511
18631
  versionAdded: null,
@@ -18586,7 +18706,7 @@ const scriptletsCompatibilityTableData = {
18586
18706
  }]
18587
18707
  }, {
18588
18708
  name: "remove-class.js",
18589
- aliases: ["rc.js"],
18709
+ aliases: ["rc.js", "rc", "remove-class"],
18590
18710
  description: null,
18591
18711
  docs: "https://github.com/gorhill/uBlock/wiki/Resources-Library#remove-classjs-",
18592
18712
  versionAdded: null,
@@ -20443,6 +20563,7 @@ const scriptletsCompatibilityTableData = {
20443
20563
  "ubo-ra.js": 59,
20444
20564
  "ubo-remove-attr": 59,
20445
20565
  "ubo-ra": 59,
20566
+ ra: 59,
20446
20567
  "remove-class": 60,
20447
20568
  "remove-class.js": 60,
20448
20569
  "ubo-remove-class.js": 60,
@@ -20450,6 +20571,7 @@ const scriptletsCompatibilityTableData = {
20450
20571
  "ubo-rc.js": 60,
20451
20572
  "ubo-remove-class": 60,
20452
20573
  "ubo-rc": 60,
20574
+ rc: 60,
20453
20575
  "remove-cookie": 61,
20454
20576
  "cookie-remover.js": 61,
20455
20577
  "ubo-cookie-remover.js": 61,
@@ -21507,6 +21629,44 @@ function getScriptletName(scriptletNode) {
21507
21629
  }
21508
21630
  return scriptletNode.children[0]?.value ?? EMPTY;
21509
21631
  }
21632
+ /**
21633
+ * Transform the nth argument of the scriptlet node
21634
+ *
21635
+ * @param scriptletNode Scriptlet node to transform argument of
21636
+ * @param index Index of the argument to transform (index 0 is the scriptlet name)
21637
+ * @param transform Function to transform the argument
21638
+ */
21639
+ function transformNthScriptletArgument(scriptletNode, index, transform) {
21640
+ const child = scriptletNode.children[index];
21641
+ if (!isUndefined(child)) {
21642
+ const transformed = transform(child?.value ?? null);
21643
+ if (isNull(transformed)) {
21644
+ // eslint-disable-next-line no-param-reassign
21645
+ scriptletNode.children[index] = null;
21646
+ return;
21647
+ }
21648
+ if (isNull(child)) {
21649
+ // eslint-disable-next-line no-param-reassign
21650
+ scriptletNode.children[index] = {
21651
+ type: 'Value',
21652
+ value: transformed
21653
+ };
21654
+ return;
21655
+ }
21656
+ child.value = transformed;
21657
+ }
21658
+ }
21659
+ /**
21660
+ * Transform all arguments of the scriptlet node
21661
+ *
21662
+ * @param scriptletNode Scriptlet node to transform arguments of
21663
+ * @param transform Function to transform the arguments
21664
+ */
21665
+ function transformAllScriptletArguments(scriptletNode, transform) {
21666
+ for (let i = 0; i < scriptletNode.children.length; i += 1) {
21667
+ transformNthScriptletArgument(scriptletNode, i, transform);
21668
+ }
21669
+ }
21510
21670
  /**
21511
21671
  * Set name of the scriptlet.
21512
21672
  * Modifies input `scriptletNode` if needed.
@@ -21515,10 +21675,7 @@ function getScriptletName(scriptletNode) {
21515
21675
  * @param name Name to set
21516
21676
  */
21517
21677
  function setScriptletName(scriptletNode, name) {
21518
- if (scriptletNode.children.length > 0 && !isNull(scriptletNode.children[0])) {
21519
- // eslint-disable-next-line no-param-reassign
21520
- scriptletNode.children[0].value = name;
21521
- }
21678
+ transformNthScriptletArgument(scriptletNode, 0, () => name);
21522
21679
  }
21523
21680
  /**
21524
21681
  * Set quote type of the scriptlet parameters
@@ -21527,1422 +21684,1761 @@ function setScriptletName(scriptletNode, name) {
21527
21684
  * @param quoteType Preferred quote type
21528
21685
  */
21529
21686
  function setScriptletQuoteType(scriptletNode, quoteType) {
21530
- if (scriptletNode.children.length > 0) {
21531
- for (let i = 0; i < scriptletNode.children.length; i += 1) {
21532
- const child = scriptletNode.children[i];
21533
- if (isNull(child)) {
21534
- continue;
21535
- }
21536
- // eslint-disable-next-line no-param-reassign
21537
- child.value = QuoteUtils.setStringQuoteType(child.value, quoteType);
21538
- }
21539
- }
21687
+ // null is a special value that means "no value", but we can't change its quote type,
21688
+ // so we need to convert it to empty string
21689
+ transformAllScriptletArguments(scriptletNode, value => QuoteUtils.setStringQuoteType(value ?? EMPTY, quoteType));
21540
21690
  }
21541
21691
 
21542
21692
  /**
21543
- * @file Scriptlet injection rule converter
21693
+ * @file Resource type schema.
21544
21694
  */
21545
- const ABP_SCRIPTLET_PREFIX = 'abp-';
21546
- const UBO_SCRIPTLET_PREFIX = 'ubo-';
21547
21695
  /**
21548
- * Scriptlet injection rule converter class
21696
+ * Resource type.
21549
21697
  *
21550
- * @todo Implement `convertToUbo` and `convertToAbp`
21698
+ * @see {@link https://developer.chrome.com/docs/extensions/reference/declarativeNetRequest/#type-ResourceType}
21551
21699
  */
21552
- class ScriptletRuleConverter extends RuleConverterBase {
21553
- /**
21554
- * Converts a scriptlet injection rule to AdGuard format, if possible.
21555
- *
21556
- * @param rule Rule node to convert
21557
- * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
21558
- * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
21559
- * If the rule was not converted, the result array will contain the original node with the same object reference
21560
- * @throws If the rule is invalid or cannot be converted
21561
- */
21562
- static convertToAdg(rule) {
21563
- // Ignore AdGuard rules
21564
- if (rule.syntax === AdblockSyntax.Adg) {
21565
- return createNodeConversionResult([rule], false);
21566
- }
21567
- const separator = rule.separator.value;
21568
- let convertedSeparator = separator;
21569
- convertedSeparator = rule.exception ? CosmeticRuleSeparator.AdgJsInjectionException : CosmeticRuleSeparator.AdgJsInjection;
21570
- const convertedScriptlets = [];
21571
- for (const scriptlet of rule.body.children) {
21572
- // Clone the node to avoid any side effects
21573
- const scriptletClone = cloneScriptletRuleNode(scriptlet);
21574
- // Remove possible quotes just to make it easier to work with the scriptlet name
21575
- const scriptletName = QuoteUtils.setStringQuoteType(getScriptletName(scriptletClone), QuoteType.None);
21576
- // Add prefix if it's not already there
21577
- let prefix;
21578
- switch (rule.syntax) {
21579
- case AdblockSyntax.Abp:
21580
- prefix = ABP_SCRIPTLET_PREFIX;
21581
- break;
21582
- case AdblockSyntax.Ubo:
21583
- prefix = UBO_SCRIPTLET_PREFIX;
21584
- break;
21585
- default:
21586
- prefix = EMPTY;
21587
- }
21588
- if (!scriptletName.startsWith(prefix)) {
21589
- setScriptletName(scriptletClone, `${prefix}${scriptletName}`);
21590
- }
21591
- // ADG scriptlet parameters should be quoted, and single quoted are preferred
21592
- setScriptletQuoteType(scriptletClone, QuoteType.Single);
21593
- convertedScriptlets.push(scriptletClone);
21594
- }
21595
- return createNodeConversionResult(convertedScriptlets.map(scriptlet => {
21596
- const res = {
21597
- category: rule.category,
21598
- type: rule.type,
21599
- syntax: AdblockSyntax.Adg,
21600
- exception: rule.exception,
21601
- domains: cloneDomainListNode(rule.domains),
21602
- separator: {
21603
- type: 'Value',
21604
- value: convertedSeparator
21605
- },
21606
- body: {
21607
- type: rule.body.type,
21608
- children: [scriptlet]
21609
- }
21610
- };
21611
- if (rule.modifiers) {
21612
- res.modifiers = cloneModifierListNode(rule.modifiers);
21613
- }
21614
- return res;
21615
- }), true);
21616
- }
21617
- }
21618
-
21700
+ var ResourceType;
21701
+ (function (ResourceType) {
21702
+ ResourceType["MainFrame"] = "main_frame";
21703
+ ResourceType["SubFrame"] = "sub_frame";
21704
+ ResourceType["Stylesheet"] = "stylesheet";
21705
+ ResourceType["Script"] = "script";
21706
+ ResourceType["Image"] = "image";
21707
+ ResourceType["Font"] = "font";
21708
+ ResourceType["Object"] = "object";
21709
+ ResourceType["XmlHttpRequest"] = "xmlhttprequest";
21710
+ ResourceType["Ping"] = "ping";
21711
+ ResourceType["Media"] = "media";
21712
+ ResourceType["WebSocket"] = "websocket";
21713
+ ResourceType["Other"] = "other";
21714
+ })(ResourceType || (ResourceType = {}));
21619
21715
  /**
21620
- * @file Utility functions for working with modifier nodes
21716
+ * Resource type schema.
21621
21717
  */
21718
+ const resourceTypeSchema = zod.nativeEnum(ResourceType);
21719
+
21622
21720
  /**
21623
- * Creates a modifier node
21721
+ * Map of resource types to their corresponding adblock modifier names.
21722
+ *
21723
+ * @note Record type is used to ensure that all resource types are present in the map.
21724
+ */
21725
+ const RESOURCE_TYPE_MODIFIER_MAP = Object.freeze({
21726
+ [ResourceType.MainFrame]: 'document',
21727
+ [ResourceType.SubFrame]: 'subdocument',
21728
+ [ResourceType.Stylesheet]: 'stylesheet',
21729
+ [ResourceType.Script]: 'script',
21730
+ [ResourceType.Image]: 'image',
21731
+ [ResourceType.Font]: 'font',
21732
+ [ResourceType.Object]: 'object',
21733
+ [ResourceType.XmlHttpRequest]: 'xmlhttprequest',
21734
+ [ResourceType.Ping]: 'ping',
21735
+ [ResourceType.Media]: 'media',
21736
+ [ResourceType.WebSocket]: 'websocket',
21737
+ [ResourceType.Other]: 'other'
21738
+ });
21739
+ /**
21740
+ * Gets the adblock modifier name for the given resource type.
21624
21741
  *
21625
- * @param name Name of the modifier
21626
- * @param value Value of the modifier
21627
- * @param exception Whether the modifier is an exception
21628
- * @returns Modifier node
21742
+ * @param resourceType Resource type to get the modifier name for.
21743
+ * @param platform Platform to get the modifier for.
21744
+ *
21745
+ * @returns A string containing the adblock modifier name for the given resource type
21746
+ * or `null` if the modifier could not be found.
21629
21747
  */
21630
- function createModifierNode(name, value = undefined, exception = false) {
21631
- const result = {
21632
- type: 'Modifier',
21633
- exception,
21634
- name: {
21635
- type: 'Value',
21636
- value: name
21637
- }
21638
- };
21639
- if (!isUndefined(value)) {
21640
- result.value = {
21641
- type: 'Value',
21642
- value
21643
- };
21748
+ const getResourceTypeModifier = (resourceType, platform) => {
21749
+ const modifierName = RESOURCE_TYPE_MODIFIER_MAP[resourceType];
21750
+ if (!modifierName) {
21751
+ return null;
21644
21752
  }
21645
- return result;
21646
- }
21753
+ const modifierData = modifiersCompatibilityTable.getFirst(modifierName, platform);
21754
+ if (isNull(modifierData)) {
21755
+ return null;
21756
+ }
21757
+ return modifierData.name;
21758
+ };
21647
21759
  /**
21648
- * Creates a modifier list node
21760
+ * Checks if the given resource type is valid.
21649
21761
  *
21650
- * @param modifiers Modifiers to put in the list (optional, defaults to an empty list)
21651
- * @returns Modifier list node
21762
+ * @param resourceType Resource type to check.
21763
+ *
21764
+ * @returns `true` if the resource type is valid, `false` otherwise.
21652
21765
  */
21653
- function createModifierListNode(modifiers = []) {
21654
- const result = {
21655
- type: 'ModifierList',
21656
- // We need to clone the modifiers to avoid side effects
21657
- children: modifiers.length ? clone(modifiers) : []
21658
- };
21659
- return result;
21660
- }
21766
+ const isValidResourceType = resourceType => {
21767
+ return Object.values(ResourceType).includes(resourceType);
21768
+ };
21661
21769
 
21662
21770
  /**
21663
- * A very simple map extension that allows to store multiple values for the same key
21664
- * by storing them in an array.
21771
+ * @file Compatibility tables for redirects.
21772
+ */
21773
+ /**
21774
+ * Prefix for resource redirection names.
21775
+ */
21776
+ const ABP_RESOURCE_PREFIX = 'abp-resource:';
21777
+ const ABP_RESOURCE_PREFIX_LENGTH = ABP_RESOURCE_PREFIX.length;
21778
+ /**
21779
+ * Normalizes the redirect name.
21665
21780
  *
21666
- * @todo Add more methods if needed
21781
+ * @param name Redirect name to normalize.
21782
+ *
21783
+ * @returns Normalized redirect name.
21784
+ *
21785
+ * @example
21786
+ * redirectNameNormalizer('abp-resource:my-resource') // => 'my-resource'
21787
+ * redirectNameNormalizer('noop.js:99') // => 'noop.js'
21667
21788
  */
21668
- class MultiValueMap extends Map {
21789
+ const redirectNameNormalizer = name => {
21790
+ // Remove ABP resource prefix, if present
21791
+ if (name.startsWith(ABP_RESOURCE_PREFIX)) {
21792
+ return name.slice(ABP_RESOURCE_PREFIX_LENGTH);
21793
+ }
21794
+ // Remove :[integer] priority suffix from the name, if present
21795
+ // See:
21796
+ // - https://github.com/AdguardTeam/tsurlfilter/issues/59
21797
+ // - https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#redirect
21798
+ const colonIndex = name.lastIndexOf(COLON);
21799
+ if (colonIndex !== -1 && /^\d+$/.test(name.slice(colonIndex + 1))) {
21800
+ return name.slice(0, colonIndex);
21801
+ }
21802
+ return name;
21803
+ };
21804
+ /**
21805
+ * Compatibility table for redirects.
21806
+ */
21807
+ class RedirectsCompatibilityTable extends CompatibilityTableBase {
21669
21808
  /**
21670
- * Adds a value to the map. If the key already exists, the value will be appended to the existing array,
21671
- * otherwise a new array will be created for the key.
21809
+ * Creates a new instance of the compatibility table for redirects.
21672
21810
  *
21673
- * @param key Key to add
21674
- * @param values Value(s) to add
21811
+ * @param data Compatibility table data.
21675
21812
  */
21676
- add(key, ...values) {
21677
- let currentValues = super.get(key);
21678
- if (isUndefined(currentValues)) {
21679
- currentValues = [];
21680
- super.set(key, values);
21813
+ constructor(data) {
21814
+ super(data, redirectNameNormalizer);
21815
+ }
21816
+ /**
21817
+ * Gets the resource type adblock modifiers for the redirect for the given platform
21818
+ * based on the `resourceTypes` field.
21819
+ *
21820
+ * @param redirect Redirect name or redirect data.
21821
+ * @param platform Platform to get the modifiers for.
21822
+ *
21823
+ * @returns Set of resource type modifiers or an empty set if the redirect is not found or has no resource types.
21824
+ */
21825
+ getResourceTypeModifiers(redirect, platform) {
21826
+ let redirectData = null;
21827
+ if (isString(redirect)) {
21828
+ redirectData = this.getFirst(redirect, platform);
21829
+ } else {
21830
+ redirectData = redirect;
21681
21831
  }
21682
- currentValues.push(...values);
21832
+ const modifierNames = new Set();
21833
+ if (isNull(redirectData) || isUndefined(redirectData.resourceTypes)) {
21834
+ return modifierNames;
21835
+ }
21836
+ for (const resourceType of redirectData.resourceTypes) {
21837
+ const modifierName = getResourceTypeModifier(resourceType, platform);
21838
+ if (isNull(modifierName)) {
21839
+ continue;
21840
+ }
21841
+ modifierNames.add(modifierName);
21842
+ }
21843
+ return modifierNames;
21683
21844
  }
21684
21845
  }
21846
+ /**
21847
+ * Deep freeze the compatibility table data to avoid accidental modifications.
21848
+ */
21849
+ deepFreeze(redirectsCompatibilityTableData);
21850
+ /**
21851
+ * Compatibility table instance for redirects.
21852
+ */
21853
+ const redirectsCompatibilityTable = new RedirectsCompatibilityTable(redirectsCompatibilityTableData);
21685
21854
 
21686
21855
  /**
21687
- * @file Cosmetic rule modifier converter from uBO to ADG
21856
+ * @file Compatibility tables for scriptlets.
21688
21857
  */
21689
- const UBO_MATCHES_PATH_OPERATOR = 'matches-path';
21690
- const ADG_PATH_MODIFIER = 'path';
21691
21858
  /**
21692
- * Special characters in modifier regexps that should be escaped
21859
+ * Compatibility table for scriptlets.
21693
21860
  */
21694
- const SPECIAL_MODIFIER_REGEX_CHARS = new Set([OPEN_SQUARE_BRACKET, CLOSE_SQUARE_BRACKET, COMMA, ESCAPE_CHARACTER]);
21861
+ class ScriptletsCompatibilityTable extends CompatibilityTableBase {}
21695
21862
  /**
21696
- * Helper class for converting cosmetic rule modifiers from uBO to ADG
21863
+ * Deep freeze the compatibility table data to avoid accidental modifications.
21697
21864
  */
21698
- class AdgCosmeticRuleModifierConverter {
21699
- /**
21700
- * Converts a uBO cosmetic rule modifier list to ADG, if possible.
21701
- *
21702
- * @param modifierList Cosmetic rule modifier list node to convert
21703
- * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
21704
- * the converted node, and its `isConverted` flag indicates whether the original node was converted.
21705
- * If the node was not converted, the result will contain the original node with the same object reference
21706
- * @throws If the modifier list cannot be converted
21707
- * @see {@link https://github.com/gorhill/uBlock/wiki/Procedural-cosmetic-filters#cosmetic-filter-operators}
21708
- */
21709
- static convertFromUbo(modifierList) {
21710
- const conversionMap = new MultiValueMap();
21711
- modifierList.children.forEach((modifier, index) => {
21712
- // :matches-path
21713
- if (modifier.name.value === UBO_MATCHES_PATH_OPERATOR) {
21714
- if (!modifier.value) {
21715
- throw new RuleConversionError(`'${UBO_MATCHES_PATH_OPERATOR}' operator requires a value`);
21716
- }
21717
- const value = RegExpUtils.isRegexPattern(modifier.value.value) ? StringUtils.escapeCharacters(modifier.value.value, SPECIAL_MODIFIER_REGEX_CHARS) : modifier.value.value;
21718
- // Convert uBO's `:matches-path(...)` operator to ADG's `$path=...` modifier
21719
- conversionMap.add(index, createModifierNode(ADG_PATH_MODIFIER,
21720
- // We should negate the regexp if the modifier is an exception
21721
- modifier.exception
21722
- // eslint-disable-next-line max-len
21723
- ? `${REGEX_MARKER}${RegExpUtils.negateRegexPattern(RegExpUtils.patternToRegexp(value))}${REGEX_MARKER}` : value));
21724
- }
21725
- });
21726
- // Check if we have any converted modifiers
21727
- if (conversionMap.size) {
21728
- const modifierListClone = clone(modifierList);
21729
- // Replace the original modifiers with the converted ones
21730
- modifierListClone.children = modifierListClone.children.map((modifier, index) => {
21731
- const convertedModifier = conversionMap.get(index);
21732
- return convertedModifier ?? modifier;
21733
- }).flat();
21734
- return createConversionResult(modifierListClone, true);
21735
- }
21736
- // Otherwise, just return the original modifier list
21737
- return createConversionResult(modifierList, false);
21738
- }
21739
- }
21740
- const ERROR_MESSAGES$1 = {
21741
- // eslint-disable-next-line max-len
21742
- INVALID_ATTRIBUTE_VALUE: `Expected '${getFormattedTokenName(TokenType$1.Ident)}' or '${getFormattedTokenName(TokenType$1.String)}' as attribute value, but got '%s' with value '%s`
21743
- };
21744
- var PseudoClasses;
21745
- (function (PseudoClasses) {
21746
- PseudoClasses["AbpContains"] = "-abp-contains";
21747
- PseudoClasses["AbpHas"] = "-abp-has";
21748
- PseudoClasses["Contains"] = "contains";
21749
- PseudoClasses["Has"] = "has";
21750
- PseudoClasses["HasText"] = "has-text";
21751
- PseudoClasses["MatchesCss"] = "matches-css";
21752
- PseudoClasses["MatchesCssAfter"] = "matches-css-after";
21753
- PseudoClasses["MatchesCssBefore"] = "matches-css-before";
21754
- PseudoClasses["Not"] = "not";
21755
- })(PseudoClasses || (PseudoClasses = {}));
21756
- var PseudoElements;
21757
- (function (PseudoElements) {
21758
- PseudoElements["After"] = "after";
21759
- PseudoElements["Before"] = "before";
21760
- })(PseudoElements || (PseudoElements = {}));
21761
- const PSEUDO_ELEMENT_NAMES = new Set([PseudoElements.After, PseudoElements.Before]);
21865
+ deepFreeze(scriptletsCompatibilityTableData);
21762
21866
  /**
21763
- * CSS selector converter
21764
- *
21765
- * @todo Implement `convertToUbo` and `convertToAbp`
21867
+ * Compatibility table instance for scriptlets.
21766
21868
  */
21767
- class CssSelectorConverter extends ConverterBase {
21768
- /**
21769
- * Converts Extended CSS elements to AdGuard-compatible ones
21770
- *
21771
- * @param selectorList Selector list to convert
21772
- * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
21773
- * the converted node, and its `isConverted` flag indicates whether the original node was converted.
21774
- * If the node was not converted, the result will contain the original node with the same object reference
21775
- * @throws If the rule is invalid or incompatible
21776
- */
21777
- static convertToAdg(selectorList) {
21778
- const stream = selectorList instanceof CssTokenStream ? selectorList : new CssTokenStream(selectorList);
21779
- const converted = [];
21780
- const convertAndPushPseudo = pseudo => {
21781
- switch (pseudo) {
21782
- case PseudoClasses.AbpContains:
21783
- case PseudoClasses.HasText:
21784
- converted.push(PseudoClasses.Contains);
21785
- converted.push(OPEN_PARENTHESIS);
21786
- break;
21787
- case PseudoClasses.AbpHas:
21788
- converted.push(PseudoClasses.Has);
21789
- converted.push(OPEN_PARENTHESIS);
21790
- break;
21791
- // a bit special case:
21792
- // - `:matches-css-before(...)` → `:matches-css(before, ...)`
21793
- // - `:matches-css-after(...)` → `:matches-css(after, ...)`
21794
- case PseudoClasses.MatchesCssBefore:
21795
- case PseudoClasses.MatchesCssAfter:
21796
- converted.push(PseudoClasses.MatchesCss);
21797
- converted.push(OPEN_PARENTHESIS);
21798
- converted.push(pseudo.substring(PseudoClasses.MatchesCss.length + 1));
21799
- converted.push(COMMA);
21800
- break;
21801
- default:
21802
- converted.push(pseudo);
21803
- converted.push(OPEN_PARENTHESIS);
21804
- break;
21805
- }
21806
- };
21807
- while (!stream.isEof()) {
21808
- const token = stream.getOrFail();
21809
- if (token.type === TokenType$1.Colon) {
21810
- // Advance colon
21811
- stream.advance();
21812
- converted.push(COLON);
21813
- const tempToken = stream.getOrFail();
21814
- // Double colon is a pseudo-element
21815
- if (tempToken.type === TokenType$1.Colon) {
21816
- stream.advance();
21817
- converted.push(COLON);
21818
- continue;
21819
- }
21820
- if (tempToken.type === TokenType$1.Ident) {
21821
- const name = stream.source.slice(tempToken.start, tempToken.end);
21822
- if (PSEUDO_ELEMENT_NAMES.has(name)) {
21823
- // Add an extra colon to the name
21824
- converted.push(COLON);
21825
- converted.push(name);
21826
- } else {
21827
- // Add the name as is
21828
- converted.push(name);
21829
- }
21830
- // Advance the names
21831
- stream.advance();
21832
- } else if (tempToken.type === TokenType$1.Function) {
21833
- const name = stream.source.slice(tempToken.start, tempToken.end - 1); // omit the last parenthesis
21834
- // :-abp-contains(...) → :contains(...)
21835
- // :has-text(...) → :contains(...)
21836
- // :-abp-has(...) → :has(...)
21837
- convertAndPushPseudo(name);
21838
- // Advance the function name
21839
- stream.advance();
21840
- }
21841
- } else if (token.type === TokenType$1.OpenSquareBracket) {
21842
- let tempToken;
21843
- const {
21844
- start
21845
- } = token;
21846
- stream.advance();
21847
- // Converts legacy Extended CSS selectors to the modern Extended CSS syntax.
21848
- // For example:
21849
- // - `[-ext-has=...]` → `:has(...)`
21850
- // - `[-ext-contains=...]` → `:contains(...)`
21851
- // - `[-ext-matches-css-before=...]` → `:matches-css(before, ...)`
21852
- stream.skipWhitespace();
21853
- stream.expect(TokenType$1.Ident);
21854
- tempToken = stream.getOrFail();
21855
- let attr = stream.source.slice(tempToken.start, tempToken.end);
21856
- // Skip if the attribute name is not a legacy Extended CSS one
21857
- if (!(attr.startsWith(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX) || attr.startsWith(ABP_EXT_CSS_PREFIX))) {
21858
- converted.push(stream.source.slice(start, tempToken.end));
21859
- stream.advance();
21860
- continue;
21861
- }
21862
- if (attr.startsWith(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX)) {
21863
- attr = attr.slice(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX.length);
21864
- }
21865
- stream.advance();
21866
- stream.skipWhitespace();
21867
- // Next token should be an equality operator (=), because Extended CSS attribute selectors
21868
- // do not support other operators
21869
- stream.expect(TokenType$1.Delim, {
21870
- value: EQUALS
21871
- });
21872
- stream.advance();
21873
- // Skip optional whitespace after the operator
21874
- stream.skipWhitespace();
21875
- // Parse attribute value
21876
- tempToken = stream.getOrFail();
21877
- // According to the spec, attribute value should be an identifier or a string
21878
- if (tempToken.type !== TokenType$1.Ident && tempToken.type !== TokenType$1.String) {
21879
- throw new Error(sprintf(ERROR_MESSAGES$1.INVALID_ATTRIBUTE_VALUE, getFormattedTokenName(tempToken.type), stream.source.slice(tempToken.start, tempToken.end)));
21880
- }
21881
- const value = stream.source.slice(tempToken.start, tempToken.end);
21882
- // Advance the attribute value
21883
- stream.advance();
21884
- // Skip optional whitespace after the attribute value
21885
- stream.skipWhitespace();
21886
- // Next character should be a closing square bracket
21887
- // We don't allow flags for Extended CSS attribute selectors
21888
- stream.expect(TokenType$1.CloseSquareBracket);
21889
- stream.advance();
21890
- converted.push(COLON);
21891
- convertAndPushPseudo(attr);
21892
- let processedValue = value.slice(1, -1); // omit the quotes
21893
- if (attr === PseudoClasses.Has) {
21894
- // TODO: Optimize this to avoid double tokenization
21895
- processedValue = CssSelectorConverter.convertToAdg(processedValue).result;
21896
- }
21897
- converted.push(processedValue);
21898
- converted.push(CLOSE_PARENTHESIS);
21899
- } else {
21900
- converted.push(stream.source.slice(token.start, token.end));
21901
- // Advance the token
21902
- stream.advance();
21903
- }
21904
- }
21905
- const convertedSelectorList = converted.join(EMPTY);
21906
- return createConversionResult(convertedSelectorList, stream.source !== convertedSelectorList);
21907
- }
21908
- }
21869
+ const scriptletsCompatibilityTable = new ScriptletsCompatibilityTable(scriptletsCompatibilityTableData);
21909
21870
 
21871
+ /* eslint-disable no-bitwise */
21910
21872
  /**
21911
- * @file CSS injection rule converter
21873
+ * @file Platform schema.
21912
21874
  */
21913
21875
  /**
21914
- * CSS injection rule converter class
21876
+ * Platform separator, e.g. 'adg_os_any|adg_safari_any' means any AdGuard OS platform and
21877
+ * any AdGuard Safari content blocker platform.
21878
+ */
21879
+ const PLATFORM_SEPARATOR = '|';
21880
+ /**
21881
+ * Platform negation character, e.g. 'adg_any|~adg_safari_any' means any AdGuard product except
21882
+ * Safari content blockers.
21883
+ */
21884
+ const PLATFORM_NEGATION = '~';
21885
+ /**
21886
+ * Parses a raw platform string into a platform bitmask.
21915
21887
  *
21916
- * @todo Implement `convertToUbo` and `convertToAbp`
21888
+ * @param rawPlatforms Raw platform string, e.g. 'adg_safari_any|adg_os_any'.
21889
+ *
21890
+ * @returns Platform bitmask.
21917
21891
  */
21918
- class CssInjectionRuleConverter extends RuleConverterBase {
21919
- /**
21920
- * Converts a CSS injection rule to AdGuard format, if possible.
21921
- *
21922
- * @param rule Rule node to convert
21923
- * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
21924
- * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
21925
- * If the rule was not converted, the result array will contain the original node with the same object reference
21926
- * @throws If the rule is invalid or cannot be converted
21927
- */
21928
- static convertToAdg(rule) {
21929
- const separator = rule.separator.value;
21930
- let convertedSeparator = separator;
21931
- const stream = new CssTokenStream(rule.body.selectorList.value);
21932
- const convertedSelectorList = CssSelectorConverter.convertToAdg(stream);
21933
- // Change the separator if the rule contains ExtendedCSS elements,
21934
- // but do not force non-extended CSS separator if the rule does not contain any ExtendedCSS selectors,
21935
- // because sometimes we use it to force executing ExtendedCSS library.
21936
- if (stream.hasAnySelectorExtendedCssNodeStrict() || rule.body.remove) {
21937
- convertedSeparator = rule.exception ? CosmeticRuleSeparator.AdgExtendedCssInjectionException : CosmeticRuleSeparator.AdgExtendedCssInjection;
21938
- } else if (rule.syntax !== AdblockSyntax.Adg) {
21939
- // If the original rule syntax is not AdGuard, use the default separator
21940
- // e.g. if the input rule is from uBO, we need to convert ## to #$#.
21941
- convertedSeparator = rule.exception ? CosmeticRuleSeparator.AdgCssInjectionException : CosmeticRuleSeparator.AdgCssInjection;
21892
+ const parseRawPlatforms = rawPlatforms => {
21893
+ // e.g. 'adg_safari_any|adg_os_any'
21894
+ const rawPlatformList = rawPlatforms.split(PLATFORM_SEPARATOR).map(rawPlatform => rawPlatform.trim());
21895
+ let result = 0;
21896
+ for (let rawPlatform of rawPlatformList) {
21897
+ // negation, e.g. 'adg_any|~adg_safari_any' means any AdGuard product except Safari content blockers
21898
+ let negated = false;
21899
+ if (rawPlatform.startsWith(PLATFORM_NEGATION)) {
21900
+ negated = true;
21901
+ rawPlatform = rawPlatform.slice(1).trim();
21942
21902
  }
21943
- // Check if the rule needs to be converted
21944
- if (!(rule.syntax === AdblockSyntax.Common || rule.syntax === AdblockSyntax.Adg) || separator !== convertedSeparator || convertedSelectorList.isConverted) {
21945
- // TODO: Replace with custom clone method
21946
- const ruleClone = clone(rule);
21947
- ruleClone.syntax = AdblockSyntax.Adg;
21948
- ruleClone.separator.value = convertedSeparator;
21949
- ruleClone.body.selectorList.value = convertedSelectorList.result;
21950
- return createNodeConversionResult([ruleClone], true);
21903
+ const platform = SPECIFIC_PLATFORM_MAP.get(rawPlatform) ?? GENERIC_PLATFORM_MAP.get(rawPlatform);
21904
+ if (isUndefined(platform)) {
21905
+ throw new Error(`Unknown platform: ${rawPlatform}`);
21906
+ }
21907
+ if (negated) {
21908
+ result &= ~platform;
21909
+ } else {
21910
+ result |= platform;
21951
21911
  }
21952
- // Otherwise, return the original rule
21953
- return createNodeConversionResult([rule], false);
21954
21912
  }
21955
- }
21956
-
21913
+ if (result === 0) {
21914
+ throw new Error('No platforms specified');
21915
+ }
21916
+ return result;
21917
+ };
21957
21918
  /**
21958
- * @file Element hiding rule converter
21919
+ * Platform schema.
21959
21920
  */
21960
- /**
21961
- * Element hiding rule converter class
21962
- *
21963
- * @todo Implement `convertToUbo` and `convertToAbp`
21964
- */
21965
- class ElementHidingRuleConverter extends RuleConverterBase {
21966
- /**
21967
- * Converts an element hiding rule to AdGuard format, if possible.
21968
- *
21969
- * @param rule Rule node to convert
21970
- * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
21971
- * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
21972
- * If the rule was not converted, the result array will contain the original node with the same object reference
21973
- * @throws If the rule is invalid or cannot be converted
21974
- */
21975
- static convertToAdg(rule) {
21976
- const separator = rule.separator.value;
21977
- let convertedSeparator = separator;
21978
- const stream = new CssTokenStream(rule.body.selectorList.value);
21979
- const convertedSelectorList = CssSelectorConverter.convertToAdg(stream);
21980
- // Change the separator if the rule contains ExtendedCSS elements,
21981
- // but do not force non-extended CSS separator if the rule does not contain any ExtendedCSS selectors,
21982
- // because sometimes we use it to force executing ExtendedCSS library.
21983
- if (stream.hasAnySelectorExtendedCssNodeStrict()) {
21984
- convertedSeparator = rule.exception ? CosmeticRuleSeparator.ExtendedElementHidingException : CosmeticRuleSeparator.ExtendedElementHiding;
21985
- }
21986
- // Check if the rule needs to be converted
21987
- if (!(rule.syntax === AdblockSyntax.Common || rule.syntax === AdblockSyntax.Adg) || separator !== convertedSeparator || convertedSelectorList.isConverted) {
21988
- // TODO: Replace with custom clone method
21989
- const ruleClone = clone(rule);
21990
- ruleClone.syntax = AdblockSyntax.Adg;
21991
- ruleClone.separator.value = convertedSeparator;
21992
- ruleClone.body.selectorList.value = convertedSelectorList.result;
21993
- return createNodeConversionResult([ruleClone], true);
21994
- }
21995
- // Otherwise, return the original rule
21996
- return createNodeConversionResult([rule], false);
21997
- }
21921
+ zod.string().min(1).transform(value => parseRawPlatforms(value));
21922
+ function getDefaultExportFromCjs(x) {
21923
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
21998
21924
  }
21925
+ var mapObj$1 = {
21926
+ exports: {}
21927
+ };
21928
+ const isObject$1 = value => typeof value === 'object' && value !== null;
21929
+ const mapObjectSkip = Symbol('skip');
21999
21930
 
22000
- /**
22001
- * @file Utility functions for working with network rule nodes
22002
- */
22003
- /**
22004
- * Creates a network rule node
22005
- *
22006
- * @param pattern Rule pattern
22007
- * @param modifiers Rule modifiers (optional, default: undefined)
22008
- * @param exception Exception rule flag (optional, default: false)
22009
- * @param syntax Adblock syntax (optional, default: Common)
22010
- * @returns Network rule node
22011
- */
22012
- function createNetworkRuleNode(pattern, modifiers = undefined, exception = false, syntax = AdblockSyntax.Common) {
22013
- const result = {
22014
- category: RuleCategory.Network,
22015
- type: NetworkRuleType.NetworkRule,
22016
- syntax,
22017
- exception,
22018
- pattern: {
22019
- type: 'Value',
22020
- value: pattern
22021
- }
21931
+ // Customized for this use-case
21932
+ const isObjectCustom = value => isObject$1(value) && !(value instanceof RegExp) && !(value instanceof Error) && !(value instanceof Date);
21933
+ const mapObject = (object, mapper, options, isSeen = new WeakMap()) => {
21934
+ options = {
21935
+ deep: false,
21936
+ target: {},
21937
+ ...options
22022
21938
  };
22023
- if (!isUndefined(modifiers)) {
22024
- result.modifiers = clone(modifiers);
21939
+ if (isSeen.has(object)) {
21940
+ return isSeen.get(object);
22025
21941
  }
22026
- return result;
22027
- }
22028
-
22029
- /**
22030
- * @file Converter for request header removal rules
22031
- */
22032
- const UBO_RESPONSEHEADER_FN = 'responseheader';
22033
- const ADG_REMOVEHEADER_MODIFIER = 'removeheader';
22034
- const ERROR_MESSAGES = {
22035
- EMPTY_PARAMETER: `Empty parameter for '${UBO_RESPONSEHEADER_FN}' function`,
22036
- EXPECTED_END_OF_RULE: "Expected end of rule, but got '%s'",
22037
- MULTIPLE_DOMAINS_NOT_SUPPORTED: 'Multiple domains are not supported yet'
22038
- };
22039
- /**
22040
- * Converter for request header removal rules
22041
- *
22042
- * @todo Implement `convertToUbo` (ABP currently doesn't support header removal rules)
22043
- */
22044
- class HeaderRemovalRuleConverter extends RuleConverterBase {
22045
- /**
22046
- * Converts a header removal rule to AdGuard syntax, if possible.
22047
- *
22048
- * @param rule Rule node to convert
22049
- * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
22050
- * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
22051
- * If the rule was not converted, the result array will contain the original node with the same object reference
22052
- * @throws If the rule is invalid or cannot be converted
22053
- * @example
22054
- * If the input rule is:
22055
- * ```adblock
22056
- * example.com##^responseheader(header-name)
22057
- * ```
22058
- * The output will be:
22059
- * ```adblock
22060
- * ||example.com^$removeheader=header-name
22061
- * ```
22062
- */
22063
- static convertToAdg(rule) {
22064
- // TODO: Add support for ABP syntax once it starts supporting header removal rules
22065
- // Leave the rule as is if it's not a header removal rule
22066
- if (rule.category !== RuleCategory.Cosmetic || rule.type !== CosmeticRuleType.HtmlFilteringRule) {
22067
- return createNodeConversionResult([rule], false);
22068
- }
22069
- const stream = new CssTokenStream(rule.body.value);
22070
- let token;
22071
- // Skip leading whitespace
22072
- stream.skipWhitespace();
22073
- // Next token should be the `^` followed by a `responseheader` function
22074
- token = stream.get();
22075
- if (!token || token.type !== TokenType$1.Delim || rule.body.value[token.start] !== UBO_HTML_MASK) {
22076
- return createNodeConversionResult([rule], false);
22077
- }
22078
- stream.advance();
22079
- token = stream.get();
22080
- if (!token) {
22081
- return createNodeConversionResult([rule], false);
22082
- }
22083
- const functionName = rule.body.value.slice(token.start, token.end - 1);
22084
- if (functionName !== UBO_RESPONSEHEADER_FN) {
22085
- return createNodeConversionResult([rule], false);
21942
+ isSeen.set(object, options.target);
21943
+ const {
21944
+ target
21945
+ } = options;
21946
+ delete options.target;
21947
+ const mapArray = array => array.map(element => isObjectCustom(element) ? mapObject(element, mapper, options, isSeen) : element);
21948
+ if (Array.isArray(object)) {
21949
+ return mapArray(object);
21950
+ }
21951
+ for (const [key, value] of Object.entries(object)) {
21952
+ const mapResult = mapper(key, value, object);
21953
+ if (mapResult === mapObjectSkip) {
21954
+ continue;
22086
21955
  }
22087
- // Parse the parameter
22088
- const paramStart = token.end;
22089
- stream.skipUntilBalanced();
22090
- const paramEnd = stream.getOrFail().end;
22091
- const param = rule.body.value.slice(paramStart, paramEnd - 1).trim();
22092
- // Do not allow empty parameter
22093
- if (param.length === 0) {
22094
- throw new RuleConversionError(ERROR_MESSAGES.EMPTY_PARAMETER);
21956
+ let [newKey, newValue, {
21957
+ shouldRecurse = true
21958
+ } = {}] = mapResult;
21959
+
21960
+ // Drop `__proto__` keys.
21961
+ if (newKey === '__proto__') {
21962
+ continue;
22095
21963
  }
22096
- stream.expect(TokenType$1.CloseParenthesis);
22097
- stream.advance();
22098
- // Skip trailing whitespace after the function call
22099
- stream.skipWhitespace();
22100
- // Expect the end of the rule - so nothing should be left in the stream
22101
- if (!stream.isEof()) {
22102
- token = stream.getOrFail();
22103
- throw new RuleConversionError(sprintf(ERROR_MESSAGES.EXPECTED_END_OF_RULE, getFormattedTokenName(token.type)));
21964
+ if (options.deep && shouldRecurse && isObjectCustom(newValue)) {
21965
+ newValue = Array.isArray(newValue) ? mapArray(newValue) : mapObject(newValue, mapper, options, isSeen);
22104
21966
  }
22105
- // Prepare network rule pattern
22106
- const pattern = [];
22107
- if (rule.domains.children.length === 1) {
22108
- // If the rule has only one domain, we can use a simple network rule pattern:
22109
- // ||single-domain-from-the-rule^
22110
- pattern.push(ADBLOCK_URL_START, rule.domains.children[0].value, ADBLOCK_URL_SEPARATOR);
22111
- } else if (rule.domains.children.length > 1) {
22112
- // TODO: Add support for multiple domains, for example:
22113
- // example.com,example.org,example.net##^responseheader(header-name)
22114
- // We should consider allowing $domain with $removeheader modifier,
22115
- // for example:
22116
- // $removeheader=header-name,domain=example.com|example.org|example.net
22117
- throw new RuleConversionError(ERROR_MESSAGES.MULTIPLE_DOMAINS_NOT_SUPPORTED);
21967
+ target[newKey] = newValue;
21968
+ }
21969
+ return target;
21970
+ };
21971
+ mapObj$1.exports = (object, mapper, options) => {
21972
+ if (!isObject$1(object)) {
21973
+ throw new TypeError(`Expected an object, got \`${object}\` (${typeof object})`);
21974
+ }
21975
+ return mapObject(object, mapper, options);
21976
+ };
21977
+ mapObj$1.exports.mapObjectSkip = mapObjectSkip;
21978
+ var mapObjExports = mapObj$1.exports;
21979
+ var camelcase = {
21980
+ exports: {}
21981
+ };
21982
+ const UPPERCASE = /[\p{Lu}]/u;
21983
+ const LOWERCASE = /[\p{Ll}]/u;
21984
+ const LEADING_CAPITAL = /^[\p{Lu}](?![\p{Lu}])/gu;
21985
+ const IDENTIFIER = /([\p{Alpha}\p{N}_]|$)/u;
21986
+ const SEPARATORS = /[_.\- ]+/;
21987
+ const LEADING_SEPARATORS = new RegExp('^' + SEPARATORS.source);
21988
+ const SEPARATORS_AND_IDENTIFIER = new RegExp(SEPARATORS.source + IDENTIFIER.source, 'gu');
21989
+ const NUMBERS_AND_IDENTIFIER = new RegExp('\\d+' + IDENTIFIER.source, 'gu');
21990
+ const preserveCamelCase = (string, toLowerCase, toUpperCase) => {
21991
+ let isLastCharLower = false;
21992
+ let isLastCharUpper = false;
21993
+ let isLastLastCharUpper = false;
21994
+ for (let i = 0; i < string.length; i++) {
21995
+ const character = string[i];
21996
+ if (isLastCharLower && UPPERCASE.test(character)) {
21997
+ string = string.slice(0, i) + '-' + string.slice(i);
21998
+ isLastCharLower = false;
21999
+ isLastLastCharUpper = isLastCharUpper;
22000
+ isLastCharUpper = true;
22001
+ i++;
22002
+ } else if (isLastCharUpper && isLastLastCharUpper && LOWERCASE.test(character)) {
22003
+ string = string.slice(0, i - 1) + '-' + string.slice(i - 1);
22004
+ isLastLastCharUpper = isLastCharUpper;
22005
+ isLastCharUpper = false;
22006
+ isLastCharLower = true;
22007
+ } else {
22008
+ isLastCharLower = toLowerCase(character) === character && toUpperCase(character) !== character;
22009
+ isLastLastCharUpper = isLastCharUpper;
22010
+ isLastCharUpper = toUpperCase(character) === character && toLowerCase(character) !== character;
22118
22011
  }
22119
- // Prepare network rule modifiers
22120
- const modifiers = createModifierListNode();
22121
- modifiers.children.push(createModifierNode(ADG_REMOVEHEADER_MODIFIER, param));
22122
- // Construct the network rule
22123
- return createNodeConversionResult([createNetworkRuleNode(pattern.join(EMPTY), modifiers,
22124
- // Copy the exception flag
22125
- rule.exception, AdblockSyntax.Adg)], true);
22126
22012
  }
22127
- }
22128
-
22129
- /**
22130
- * @file Cosmetic rule converter
22131
- */
22132
- /**
22133
- * Cosmetic rule converter class (also known as "non-basic rule converter")
22134
- *
22135
- * @todo Implement `convertToUbo` and `convertToAbp`
22136
- */
22137
- class CosmeticRuleConverter extends RuleConverterBase {
22138
- /**
22139
- * Converts a cosmetic rule to AdGuard syntax, if possible.
22140
- *
22141
- * @param rule Rule node to convert
22142
- * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
22143
- * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
22144
- * If the rule was not converted, the result array will contain the original node with the same object reference
22145
- * @throws If the rule is invalid or cannot be converted
22146
- */
22147
- static convertToAdg(rule) {
22148
- let subconverterResult;
22149
- // Convert cosmetic rule based on its type
22150
- switch (rule.type) {
22151
- case CosmeticRuleType.ElementHidingRule:
22152
- subconverterResult = ElementHidingRuleConverter.convertToAdg(rule);
22153
- break;
22154
- case CosmeticRuleType.ScriptletInjectionRule:
22155
- subconverterResult = ScriptletRuleConverter.convertToAdg(rule);
22156
- break;
22157
- case CosmeticRuleType.CssInjectionRule:
22158
- subconverterResult = CssInjectionRuleConverter.convertToAdg(rule);
22159
- break;
22160
- case CosmeticRuleType.HtmlFilteringRule:
22161
- // Handle special case: uBO response header filtering rule
22162
- // TODO: Optimize double CSS tokenization here
22163
- subconverterResult = HeaderRemovalRuleConverter.convertToAdg(rule);
22164
- if (subconverterResult.isConverted) {
22165
- break;
22166
- }
22167
- subconverterResult = HtmlRuleConverter.convertToAdg(rule);
22168
- break;
22169
- // Note: Currently, only ADG supports JS injection rules, so we don't need to convert them
22170
- case CosmeticRuleType.JsInjectionRule:
22171
- subconverterResult = createNodeConversionResult([rule], false);
22172
- break;
22173
- default:
22174
- throw new RuleConversionError('Unsupported cosmetic rule type');
22013
+ return string;
22014
+ };
22015
+ const preserveConsecutiveUppercase = (input, toLowerCase) => {
22016
+ LEADING_CAPITAL.lastIndex = 0;
22017
+ return input.replace(LEADING_CAPITAL, m1 => toLowerCase(m1));
22018
+ };
22019
+ const postProcess = (input, toUpperCase) => {
22020
+ SEPARATORS_AND_IDENTIFIER.lastIndex = 0;
22021
+ NUMBERS_AND_IDENTIFIER.lastIndex = 0;
22022
+ return input.replace(SEPARATORS_AND_IDENTIFIER, (_, identifier) => toUpperCase(identifier)).replace(NUMBERS_AND_IDENTIFIER, m => toUpperCase(m));
22023
+ };
22024
+ const camelCase$1 = (input, options) => {
22025
+ if (!(typeof input === 'string' || Array.isArray(input))) {
22026
+ throw new TypeError('Expected the input to be `string | string[]`');
22027
+ }
22028
+ options = {
22029
+ pascalCase: false,
22030
+ preserveConsecutiveUppercase: false,
22031
+ ...options
22032
+ };
22033
+ if (Array.isArray(input)) {
22034
+ input = input.map(x => x.trim()).filter(x => x.length).join('-');
22035
+ } else {
22036
+ input = input.trim();
22037
+ }
22038
+ if (input.length === 0) {
22039
+ return '';
22040
+ }
22041
+ const toLowerCase = options.locale === false ? string => string.toLowerCase() : string => string.toLocaleLowerCase(options.locale);
22042
+ const toUpperCase = options.locale === false ? string => string.toUpperCase() : string => string.toLocaleUpperCase(options.locale);
22043
+ if (input.length === 1) {
22044
+ return options.pascalCase ? toUpperCase(input) : toLowerCase(input);
22045
+ }
22046
+ const hasUpperCase = input !== toLowerCase(input);
22047
+ if (hasUpperCase) {
22048
+ input = preserveCamelCase(input, toLowerCase, toUpperCase);
22049
+ }
22050
+ input = input.replace(LEADING_SEPARATORS, '');
22051
+ if (options.preserveConsecutiveUppercase) {
22052
+ input = preserveConsecutiveUppercase(input, toLowerCase);
22053
+ } else {
22054
+ input = toLowerCase(input);
22055
+ }
22056
+ if (options.pascalCase) {
22057
+ input = toUpperCase(input.charAt(0)) + input.slice(1);
22058
+ }
22059
+ return postProcess(input, toUpperCase);
22060
+ };
22061
+ camelcase.exports = camelCase$1;
22062
+ // TODO: Remove this for the next major release
22063
+ camelcase.exports.default = camelCase$1;
22064
+ var camelcaseExports = camelcase.exports;
22065
+ class QuickLRU {
22066
+ constructor(options = {}) {
22067
+ if (!(options.maxSize && options.maxSize > 0)) {
22068
+ throw new TypeError('`maxSize` must be a number greater than 0');
22175
22069
  }
22176
- let convertedModifiers;
22177
- // Convert cosmetic rule modifiers, if any
22178
- if (rule.modifiers) {
22179
- if (rule.syntax === AdblockSyntax.Ubo) {
22180
- // uBO doesn't support this rule:
22181
- // example.com##+js(set-constant.js, foo, bar):matches-path(/baz)
22182
- if (rule.type === CosmeticRuleType.ScriptletInjectionRule) {
22183
- throw new RuleConversionError('uBO scriptlet injection rules don\'t support cosmetic rule modifiers');
22070
+ this.maxSize = options.maxSize;
22071
+ this.onEviction = options.onEviction;
22072
+ this.cache = new Map();
22073
+ this.oldCache = new Map();
22074
+ this._size = 0;
22075
+ }
22076
+ _set(key, value) {
22077
+ this.cache.set(key, value);
22078
+ this._size++;
22079
+ if (this._size >= this.maxSize) {
22080
+ this._size = 0;
22081
+ if (typeof this.onEviction === 'function') {
22082
+ for (const [key, value] of this.oldCache.entries()) {
22083
+ this.onEviction(key, value);
22184
22084
  }
22185
- convertedModifiers = AdgCosmeticRuleModifierConverter.convertFromUbo(rule.modifiers);
22186
- } else if (rule.syntax === AdblockSyntax.Abp) {
22187
- // TODO: Implement once ABP starts supporting cosmetic rule modifiers
22188
- throw new RuleConversionError('ABP don\'t support cosmetic rule modifiers');
22189
22085
  }
22086
+ this.oldCache = this.cache;
22087
+ this.cache = new Map();
22190
22088
  }
22191
- if (subconverterResult.result.length > 1 || subconverterResult.isConverted || convertedModifiers && convertedModifiers.isConverted) {
22192
- // Add modifier list to the subconverter result rules
22193
- subconverterResult.result.forEach(subconverterRule => {
22194
- if (convertedModifiers && subconverterRule.category === RuleCategory.Cosmetic) {
22195
- // eslint-disable-next-line no-param-reassign
22196
- subconverterRule.modifiers = convertedModifiers.result;
22197
- }
22198
- });
22199
- return subconverterResult;
22089
+ }
22090
+ get(key) {
22091
+ if (this.cache.has(key)) {
22092
+ return this.cache.get(key);
22093
+ }
22094
+ if (this.oldCache.has(key)) {
22095
+ const value = this.oldCache.get(key);
22096
+ this.oldCache.delete(key);
22097
+ this._set(key, value);
22098
+ return value;
22200
22099
  }
22201
- return createNodeConversionResult([rule], false);
22202
22100
  }
22203
- }
22204
-
22205
- /**
22206
- * @file Compatibility tables for redirects.
22207
- */
22208
- /**
22209
- * Prefix for resource redirection names.
22210
- */
22211
- const ABP_RESOURCE_PREFIX = 'abp-resource:';
22212
- const ABP_RESOURCE_PREFIX_LENGTH = ABP_RESOURCE_PREFIX.length;
22213
- /**
22214
- * Transforms the name of an ABP redirect to a normalized form.
22215
- *
22216
- * @param name Redirect name to normalize.
22217
- *
22218
- * @returns Normalized redirect name.
22219
- *
22220
- * @example
22221
- * abpRedirectNameNormalizer('abp-resource:my-resource') // => 'my-resource'
22222
- */
22223
- const abpRedirectNameNormalizer = name => {
22224
- if (name.startsWith(ABP_RESOURCE_PREFIX)) {
22225
- return name.slice(ABP_RESOURCE_PREFIX_LENGTH);
22101
+ set(key, value) {
22102
+ if (this.cache.has(key)) {
22103
+ this.cache.set(key, value);
22104
+ } else {
22105
+ this._set(key, value);
22106
+ }
22107
+ return this;
22226
22108
  }
22227
- return name;
22228
- };
22229
- /**
22230
- * Compatibility table for redirects.
22231
- */
22232
- class RedirectsCompatibilityTable extends CompatibilityTableBase {
22233
- /**
22234
- * Creates a new instance of the compatibility table for redirects.
22235
- *
22236
- * @param data Compatibility table data.
22237
- */
22238
- constructor(data) {
22239
- super(data, abpRedirectNameNormalizer);
22109
+ has(key) {
22110
+ return this.cache.has(key) || this.oldCache.has(key);
22240
22111
  }
22241
- }
22242
- /**
22243
- * Deep freeze the compatibility table data to avoid accidental modifications.
22244
- */
22245
- deepFreeze(redirectsCompatibilityTableData);
22246
- /**
22247
- * Compatibility table instance for redirects.
22248
- */
22249
- const redirectsCompatibilityTable = new RedirectsCompatibilityTable(redirectsCompatibilityTableData);
22250
-
22251
- /**
22252
- * @file Compatibility tables for scriptlets.
22253
- */
22254
- /**
22255
- * Compatibility table for scriptlets.
22256
- */
22257
- class ScriptletsCompatibilityTable extends CompatibilityTableBase {}
22258
- /**
22259
- * Deep freeze the compatibility table data to avoid accidental modifications.
22260
- */
22261
- deepFreeze(scriptletsCompatibilityTableData);
22262
- /**
22263
- * Compatibility table instance for scriptlets.
22264
- */
22265
- const scriptletsCompatibilityTable = new ScriptletsCompatibilityTable(scriptletsCompatibilityTableData);
22266
-
22267
- /* eslint-disable no-bitwise */
22268
- /**
22269
- * @file Platform schema.
22270
- */
22271
- /**
22272
- * Platform separator, e.g. 'adg_os_any|adg_safari_any' means any AdGuard OS platform and
22273
- * any AdGuard Safari content blocker platform.
22274
- */
22275
- const PLATFORM_SEPARATOR = '|';
22276
- /**
22277
- * Platform negation character, e.g. 'adg_any|~adg_safari_any' means any AdGuard product except
22278
- * Safari content blockers.
22279
- */
22280
- const PLATFORM_NEGATION = '~';
22281
- /**
22282
- * Parses a raw platform string into a platform bitmask.
22283
- *
22284
- * @param rawPlatforms Raw platform string, e.g. 'adg_safari_any|adg_os_any'.
22285
- *
22286
- * @returns Platform bitmask.
22287
- */
22288
- const parseRawPlatforms = rawPlatforms => {
22289
- // e.g. 'adg_safari_any|adg_os_any'
22290
- const rawPlatformList = rawPlatforms.split(PLATFORM_SEPARATOR).map(rawPlatform => rawPlatform.trim());
22291
- let result = 0;
22292
- for (let rawPlatform of rawPlatformList) {
22293
- // negation, e.g. 'adg_any|~adg_safari_any' means any AdGuard product except Safari content blockers
22294
- let negated = false;
22295
- if (rawPlatform.startsWith(PLATFORM_NEGATION)) {
22296
- negated = true;
22297
- rawPlatform = rawPlatform.slice(1).trim();
22298
- }
22299
- const platform = SPECIFIC_PLATFORM_MAP.get(rawPlatform) ?? GENERIC_PLATFORM_MAP.get(rawPlatform);
22300
- if (isUndefined(platform)) {
22301
- throw new Error(`Unknown platform: ${rawPlatform}`);
22112
+ peek(key) {
22113
+ if (this.cache.has(key)) {
22114
+ return this.cache.get(key);
22302
22115
  }
22303
- if (negated) {
22304
- result &= ~platform;
22305
- } else {
22306
- result |= platform;
22116
+ if (this.oldCache.has(key)) {
22117
+ return this.oldCache.get(key);
22307
22118
  }
22308
22119
  }
22309
- if (result === 0) {
22310
- throw new Error('No platforms specified');
22120
+ delete(key) {
22121
+ const deleted = this.cache.delete(key);
22122
+ if (deleted) {
22123
+ this._size--;
22124
+ }
22125
+ return this.oldCache.delete(key) || deleted;
22311
22126
  }
22312
- return result;
22313
- };
22314
- /**
22315
- * Platform schema.
22316
- */
22317
- zod.string().min(1).transform(value => parseRawPlatforms(value));
22318
- function getDefaultExportFromCjs(x) {
22319
- return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
22320
- }
22321
- var mapObj$1 = {
22322
- exports: {}
22323
- };
22324
- const isObject$1 = value => typeof value === 'object' && value !== null;
22325
- const mapObjectSkip = Symbol('skip');
22326
-
22327
- // Customized for this use-case
22328
- const isObjectCustom = value => isObject$1(value) && !(value instanceof RegExp) && !(value instanceof Error) && !(value instanceof Date);
22329
- const mapObject = (object, mapper, options, isSeen = new WeakMap()) => {
22330
- options = {
22331
- deep: false,
22332
- target: {},
22333
- ...options
22334
- };
22335
- if (isSeen.has(object)) {
22336
- return isSeen.get(object);
22127
+ clear() {
22128
+ this.cache.clear();
22129
+ this.oldCache.clear();
22130
+ this._size = 0;
22337
22131
  }
22338
- isSeen.set(object, options.target);
22339
- const {
22340
- target
22341
- } = options;
22342
- delete options.target;
22343
- const mapArray = array => array.map(element => isObjectCustom(element) ? mapObject(element, mapper, options, isSeen) : element);
22344
- if (Array.isArray(object)) {
22345
- return mapArray(object);
22132
+ *keys() {
22133
+ for (const [key] of this) {
22134
+ yield key;
22135
+ }
22346
22136
  }
22347
- for (const [key, value] of Object.entries(object)) {
22348
- const mapResult = mapper(key, value, object);
22349
- if (mapResult === mapObjectSkip) {
22350
- continue;
22137
+ *values() {
22138
+ for (const [, value] of this) {
22139
+ yield value;
22351
22140
  }
22352
- let [newKey, newValue, {
22353
- shouldRecurse = true
22354
- } = {}] = mapResult;
22355
-
22356
- // Drop `__proto__` keys.
22357
- if (newKey === '__proto__') {
22358
- continue;
22141
+ }
22142
+ *[Symbol.iterator]() {
22143
+ for (const item of this.cache) {
22144
+ yield item;
22359
22145
  }
22360
- if (options.deep && shouldRecurse && isObjectCustom(newValue)) {
22361
- newValue = Array.isArray(newValue) ? mapArray(newValue) : mapObject(newValue, mapper, options, isSeen);
22146
+ for (const item of this.oldCache) {
22147
+ const [key] = item;
22148
+ if (!this.cache.has(key)) {
22149
+ yield item;
22150
+ }
22362
22151
  }
22363
- target[newKey] = newValue;
22364
- }
22365
- return target;
22366
- };
22367
- mapObj$1.exports = (object, mapper, options) => {
22368
- if (!isObject$1(object)) {
22369
- throw new TypeError(`Expected an object, got \`${object}\` (${typeof object})`);
22370
22152
  }
22371
- return mapObject(object, mapper, options);
22372
- };
22373
- mapObj$1.exports.mapObjectSkip = mapObjectSkip;
22374
- var mapObjExports = mapObj$1.exports;
22375
- var camelcase = {
22376
- exports: {}
22377
- };
22378
- const UPPERCASE = /[\p{Lu}]/u;
22379
- const LOWERCASE = /[\p{Ll}]/u;
22380
- const LEADING_CAPITAL = /^[\p{Lu}](?![\p{Lu}])/gu;
22381
- const IDENTIFIER = /([\p{Alpha}\p{N}_]|$)/u;
22382
- const SEPARATORS = /[_.\- ]+/;
22383
- const LEADING_SEPARATORS = new RegExp('^' + SEPARATORS.source);
22384
- const SEPARATORS_AND_IDENTIFIER = new RegExp(SEPARATORS.source + IDENTIFIER.source, 'gu');
22385
- const NUMBERS_AND_IDENTIFIER = new RegExp('\\d+' + IDENTIFIER.source, 'gu');
22386
- const preserveCamelCase = (string, toLowerCase, toUpperCase) => {
22387
- let isLastCharLower = false;
22388
- let isLastCharUpper = false;
22389
- let isLastLastCharUpper = false;
22390
- for (let i = 0; i < string.length; i++) {
22391
- const character = string[i];
22392
- if (isLastCharLower && UPPERCASE.test(character)) {
22393
- string = string.slice(0, i) + '-' + string.slice(i);
22394
- isLastCharLower = false;
22395
- isLastLastCharUpper = isLastCharUpper;
22396
- isLastCharUpper = true;
22397
- i++;
22398
- } else if (isLastCharUpper && isLastLastCharUpper && LOWERCASE.test(character)) {
22399
- string = string.slice(0, i - 1) + '-' + string.slice(i - 1);
22400
- isLastLastCharUpper = isLastCharUpper;
22401
- isLastCharUpper = false;
22402
- isLastCharLower = true;
22403
- } else {
22404
- isLastCharLower = toLowerCase(character) === character && toUpperCase(character) !== character;
22405
- isLastLastCharUpper = isLastCharUpper;
22406
- isLastCharUpper = toUpperCase(character) === character && toLowerCase(character) !== character;
22153
+ get size() {
22154
+ let oldCacheSize = 0;
22155
+ for (const key of this.oldCache.keys()) {
22156
+ if (!this.cache.has(key)) {
22157
+ oldCacheSize++;
22158
+ }
22407
22159
  }
22160
+ return Math.min(this._size + oldCacheSize, this.maxSize);
22408
22161
  }
22409
- return string;
22410
- };
22411
- const preserveConsecutiveUppercase = (input, toLowerCase) => {
22412
- LEADING_CAPITAL.lastIndex = 0;
22413
- return input.replace(LEADING_CAPITAL, m1 => toLowerCase(m1));
22414
- };
22415
- const postProcess = (input, toUpperCase) => {
22416
- SEPARATORS_AND_IDENTIFIER.lastIndex = 0;
22417
- NUMBERS_AND_IDENTIFIER.lastIndex = 0;
22418
- return input.replace(SEPARATORS_AND_IDENTIFIER, (_, identifier) => toUpperCase(identifier)).replace(NUMBERS_AND_IDENTIFIER, m => toUpperCase(m));
22419
- };
22420
- const camelCase$1 = (input, options) => {
22421
- if (!(typeof input === 'string' || Array.isArray(input))) {
22422
- throw new TypeError('Expected the input to be `string | string[]`');
22162
+ }
22163
+ var quickLru = QuickLRU;
22164
+ const mapObj = mapObjExports;
22165
+ const camelCase = camelcaseExports;
22166
+ const QuickLru = quickLru;
22167
+ const has = (array, key) => array.some(x => {
22168
+ if (typeof x === 'string') {
22169
+ return x === key;
22170
+ }
22171
+ x.lastIndex = 0;
22172
+ return x.test(key);
22173
+ });
22174
+ const cache = new QuickLru({
22175
+ maxSize: 100000
22176
+ });
22177
+
22178
+ // Reproduces behavior from `map-obj`
22179
+ const isObject = value => typeof value === 'object' && value !== null && !(value instanceof RegExp) && !(value instanceof Error) && !(value instanceof Date);
22180
+ const camelCaseConvert = (input, options) => {
22181
+ if (!isObject(input)) {
22182
+ return input;
22423
22183
  }
22424
22184
  options = {
22185
+ deep: false,
22425
22186
  pascalCase: false,
22426
- preserveConsecutiveUppercase: false,
22427
22187
  ...options
22428
22188
  };
22189
+ const {
22190
+ exclude,
22191
+ pascalCase,
22192
+ stopPaths,
22193
+ deep
22194
+ } = options;
22195
+ const stopPathsSet = new Set(stopPaths);
22196
+ const makeMapper = parentPath => (key, value) => {
22197
+ if (deep && isObject(value)) {
22198
+ const path = parentPath === undefined ? key : `${parentPath}.${key}`;
22199
+ if (!stopPathsSet.has(path)) {
22200
+ value = mapObj(value, makeMapper(path));
22201
+ }
22202
+ }
22203
+ if (!(exclude && has(exclude, key))) {
22204
+ const cacheKey = pascalCase ? `${key}_` : key;
22205
+ if (cache.has(cacheKey)) {
22206
+ key = cache.get(cacheKey);
22207
+ } else {
22208
+ const returnValue = camelCase(key, {
22209
+ pascalCase,
22210
+ locale: false
22211
+ });
22212
+ if (key.length < 100) {
22213
+ // Prevent abuse
22214
+ cache.set(cacheKey, returnValue);
22215
+ }
22216
+ key = returnValue;
22217
+ }
22218
+ }
22219
+ return [key, value];
22220
+ };
22221
+ return mapObj(input, makeMapper(undefined));
22222
+ };
22223
+ var camelcaseKeys = (input, options) => {
22429
22224
  if (Array.isArray(input)) {
22430
- input = input.map(x => x.trim()).filter(x => x.length).join('-');
22431
- } else {
22432
- input = input.trim();
22433
- }
22434
- if (input.length === 0) {
22435
- return '';
22436
- }
22437
- const toLowerCase = options.locale === false ? string => string.toLowerCase() : string => string.toLocaleLowerCase(options.locale);
22438
- const toUpperCase = options.locale === false ? string => string.toUpperCase() : string => string.toLocaleUpperCase(options.locale);
22439
- if (input.length === 1) {
22440
- return options.pascalCase ? toUpperCase(input) : toLowerCase(input);
22225
+ return Object.keys(input).map(key => camelCaseConvert(input[key], options));
22441
22226
  }
22442
- const hasUpperCase = input !== toLowerCase(input);
22443
- if (hasUpperCase) {
22444
- input = preserveCamelCase(input, toLowerCase, toUpperCase);
22227
+ return camelCaseConvert(input, options);
22228
+ };
22229
+ var camelCaseKeys = /*@__PURE__*/getDefaultExportFromCjs(camelcaseKeys);
22230
+
22231
+ /**
22232
+ * @file Zod camelCase utility.
22233
+ */
22234
+ // eslint-disable-next-line import/no-extraneous-dependencies
22235
+ /**
22236
+ * Transforms Zod schema to camelCase.
22237
+ *
22238
+ * @param zod Zod schema.
22239
+ *
22240
+ * @returns Zod schema with camelCase properties.
22241
+ *
22242
+ * @see {@link https://github.com/colinhacks/zod/issues/486#issuecomment-1501097361}
22243
+ */
22244
+ const zodToCamelCase = zod => {
22245
+ return zod.transform(val => camelCaseKeys(val));
22246
+ };
22247
+
22248
+ /**
22249
+ * @file Base compatibility data schema, which is commonly used in compatibility tables.
22250
+ */
22251
+ /**
22252
+ * Zod schema for boolean values. Accepts both boolean and string values.
22253
+ */
22254
+ const booleanSchema = zod.union([zod.string().transform(val => val.trim().toLowerCase() === 'true'), zod.boolean()]);
22255
+ /**
22256
+ * Zod schema for non-empty string values.
22257
+ */
22258
+ const nonEmptyStringSchema = zod.string().transform(val => val.trim()).pipe(zod.string().min(1));
22259
+ /**
22260
+ * Zod schema for base compatibility data.
22261
+ * Here we use snake_case properties because the compatibility data is stored in YAML files.
22262
+ */
22263
+ const baseCompatibilityDataSchema = zod.object({
22264
+ /**
22265
+ * Name of the actual entity.
22266
+ */
22267
+ name: nonEmptyStringSchema,
22268
+ /**
22269
+ * List of aliases for the entity (if any).
22270
+ */
22271
+ aliases: zod.array(nonEmptyStringSchema).nullable().default(null),
22272
+ /**
22273
+ * Short description of the actual entity.
22274
+ * If not specified or it's value is `null`, then the description is not available.
22275
+ */
22276
+ description: nonEmptyStringSchema.nullable().default(null),
22277
+ /**
22278
+ * Link to the documentation. If not specified or it's value is `null`, then the documentation is not available.
22279
+ */
22280
+ docs: nonEmptyStringSchema.nullable().default(null),
22281
+ /**
22282
+ * The version of the adblocker in which the entity was added.
22283
+ * For AdGuard resources, the version of the library is specified.
22284
+ */
22285
+ version_added: nonEmptyStringSchema.nullable().default(null),
22286
+ /**
22287
+ * The version of the adblocker when the entity was removed.
22288
+ */
22289
+ version_removed: nonEmptyStringSchema.nullable().default(null),
22290
+ /**
22291
+ * Describes whether the entity is deprecated.
22292
+ */
22293
+ deprecated: booleanSchema.default(false),
22294
+ /**
22295
+ * Message that describes why the entity is deprecated.
22296
+ * If not specified or it's value is `null`, then the message is not available.
22297
+ * It's value is omitted if the entity is not marked as deprecated.
22298
+ */
22299
+ deprecation_message: nonEmptyStringSchema.nullable().default(null),
22300
+ /**
22301
+ * Describes whether the entity is removed; for *already removed* features.
22302
+ */
22303
+ removed: booleanSchema.default(false),
22304
+ /**
22305
+ * Message that describes why the entity is removed.
22306
+ * If not specified or it's value is `null`, then the message is not available.
22307
+ * It's value is omitted if the entity is not marked as deprecated.
22308
+ */
22309
+ removal_message: nonEmptyStringSchema.nullable().default(null)
22310
+ });
22311
+ /**
22312
+ * Zod schema for base compatibility data with camelCase properties.
22313
+ */
22314
+ zodToCamelCase(baseCompatibilityDataSchema);
22315
+ /**
22316
+ * Refinement logic for base compatibility data.
22317
+ *
22318
+ * @param data Base compatibility data.
22319
+ * @param ctx Refinement context.
22320
+ */
22321
+ const baseRefineLogic = (data, ctx) => {
22322
+ if (data.deprecated && !data.deprecation_message) {
22323
+ ctx.addIssue({
22324
+ code: zod.ZodIssueCode.custom,
22325
+ message: 'deprecation_message is required for deprecated modifiers'
22326
+ });
22445
22327
  }
22446
- input = input.replace(LEADING_SEPARATORS, '');
22447
- if (options.preserveConsecutiveUppercase) {
22448
- input = preserveConsecutiveUppercase(input, toLowerCase);
22449
- } else {
22450
- input = toLowerCase(input);
22328
+ if (!data.deprecated && data.deprecation_message) {
22329
+ ctx.addIssue({
22330
+ code: zod.ZodIssueCode.custom,
22331
+ message: 'deprecation_message is only allowed for deprecated modifiers'
22332
+ });
22451
22333
  }
22452
- if (options.pascalCase) {
22453
- input = toUpperCase(input.charAt(0)) + input.slice(1);
22334
+ if (data.aliases && data.aliases.length !== new Set(data.aliases).size) {
22335
+ ctx.addIssue({
22336
+ code: zod.ZodIssueCode.custom,
22337
+ message: 'Aliases must be unique'
22338
+ });
22454
22339
  }
22455
- return postProcess(input, toUpperCase);
22456
22340
  };
22457
- camelcase.exports = camelCase$1;
22458
- // TODO: Remove this for the next major release
22459
- camelcase.exports.default = camelCase$1;
22460
- var camelcaseExports = camelcase.exports;
22461
- class QuickLRU {
22462
- constructor(options = {}) {
22463
- if (!(options.maxSize && options.maxSize > 0)) {
22464
- throw new TypeError('`maxSize` must be a number greater than 0');
22465
- }
22466
- this.maxSize = options.maxSize;
22467
- this.onEviction = options.onEviction;
22468
- this.cache = new Map();
22469
- this.oldCache = new Map();
22470
- this._size = 0;
22341
+
22342
+ /**
22343
+ * Checks if error has message.
22344
+ *
22345
+ * @param error Error object.
22346
+ * @returns If param is error.
22347
+ */
22348
+ function isErrorWithMessage(error) {
22349
+ return typeof error === 'object' && error !== null && 'message' in error && typeof error.message === 'string';
22350
+ }
22351
+ /**
22352
+ * Converts error to the error with message.
22353
+ *
22354
+ * @param maybeError Possible error.
22355
+ * @returns Error with message.
22356
+ */
22357
+ function toErrorWithMessage(maybeError) {
22358
+ if (isErrorWithMessage(maybeError)) {
22359
+ return maybeError;
22471
22360
  }
22472
- _set(key, value) {
22473
- this.cache.set(key, value);
22474
- this._size++;
22475
- if (this._size >= this.maxSize) {
22476
- this._size = 0;
22477
- if (typeof this.onEviction === 'function') {
22478
- for (const [key, value] of this.oldCache.entries()) {
22479
- this.onEviction(key, value);
22480
- }
22481
- }
22482
- this.oldCache = this.cache;
22483
- this.cache = new Map();
22484
- }
22361
+ try {
22362
+ return new Error(JSON.stringify(maybeError));
22363
+ } catch {
22364
+ // fallback in case there's an error stringifying the maybeError
22365
+ // like with circular references for example.
22366
+ return new Error(String(maybeError));
22367
+ }
22368
+ }
22369
+ /**
22370
+ * Converts error object to error with message. This method might be helpful to handle thrown errors.
22371
+ *
22372
+ * @param error Error object.
22373
+ *
22374
+ * @returns Message of the error.
22375
+ */
22376
+ function getErrorMessage(error) {
22377
+ return toErrorWithMessage(error).message;
22378
+ }
22379
+
22380
+ /**
22381
+ * @file Schema for modifier data.
22382
+ */
22383
+ /**
22384
+ * Known validators that don't need to be validated as regex.
22385
+ */
22386
+ const KNOWN_VALIDATORS = new Set(['domain', 'pipe_separated_domains', 'regexp', 'url']);
22387
+ /**
22388
+ * Zod schema for modifier data.
22389
+ */
22390
+ zodToCamelCase(baseCompatibilityDataSchema.extend({
22391
+ /**
22392
+ * List of modifiers that are incompatible with the actual one.
22393
+ */
22394
+ conflicts: zod.array(nonEmptyStringSchema).nullable().default(null),
22395
+ /**
22396
+ * The actual modifier is incompatible with all other modifiers, except the ones listed in `conflicts`.
22397
+ */
22398
+ inverse_conflicts: booleanSchema.default(false),
22399
+ /**
22400
+ * Describes whether the actual modifier supports value assignment. For example, `$domain` is assignable,
22401
+ * so it can be used like this: `$domain=domain.com\|~subdomain.domain.com`, where `=` is the assignment operator
22402
+ * and `domain.com\|~subdomain.domain.com` is the value.
22403
+ */
22404
+ assignable: booleanSchema.default(false),
22405
+ /**
22406
+ * Describes whether the actual modifier can be negated. For example, `$third-party` is negatable,
22407
+ * so it can be used like this: `$~third-party`.
22408
+ */
22409
+ negatable: booleanSchema.default(true),
22410
+ /**
22411
+ * The actual modifier can only be used in blocking rules, it cannot be used in exceptions.
22412
+ * If it's value is `true`, then the modifier can be used only in blocking rules.
22413
+ * `exception_only` and `block_only` cannot be used together (they are mutually exclusive).
22414
+ */
22415
+ block_only: booleanSchema.default(false),
22416
+ /**
22417
+ * The actual modifier can only be used in exceptions, it cannot be used in blocking rules.
22418
+ * If it's value is `true`, then the modifier can be used only in exceptions.
22419
+ * `exception_only` and `block_only` cannot be used together (they are mutually exclusive).
22420
+ */
22421
+ exception_only: booleanSchema.default(false),
22422
+ /**
22423
+ * Describes whether the *assignable* modifier value is required.
22424
+ * For example, `$cookie` is assignable but it can be used without a value in exception rules:
22425
+ * `@@\|\|example.com^$cookie`.
22426
+ * If `false`, the `value_format` is required, e.g. the value of `$app` should always be specified
22427
+ */
22428
+ value_optional: booleanSchema.default(false),
22429
+ /**
22430
+ * Describes the format of the value for the *assignable* modifier.
22431
+ * Its value can be a regex pattern or a known validator name (e.g. `domain`, `pipe_separated_domains`, etc.).
22432
+ */
22433
+ value_format: nonEmptyStringSchema.nullable().default(null)
22434
+ }).superRefine((data, ctx) => {
22435
+ // TODO: find something better, for now we can't add refine logic to the base schema:
22436
+ // https://github.com/colinhacks/zod/issues/454#issuecomment-848370721
22437
+ baseRefineLogic(data, ctx);
22438
+ if (data.block_only && data.exception_only) {
22439
+ ctx.addIssue({
22440
+ code: zod.ZodIssueCode.custom,
22441
+ message: 'block_only and exception_only are mutually exclusive'
22442
+ });
22485
22443
  }
22486
- get(key) {
22487
- if (this.cache.has(key)) {
22488
- return this.cache.get(key);
22489
- }
22490
- if (this.oldCache.has(key)) {
22491
- const value = this.oldCache.get(key);
22492
- this.oldCache.delete(key);
22493
- this._set(key, value);
22494
- return value;
22495
- }
22444
+ if (data.assignable && !data.value_format) {
22445
+ ctx.addIssue({
22446
+ code: zod.ZodIssueCode.custom,
22447
+ message: 'value_format is required for assignable modifiers'
22448
+ });
22496
22449
  }
22497
- set(key, value) {
22498
- if (this.cache.has(key)) {
22499
- this.cache.set(key, value);
22500
- } else {
22501
- this._set(key, value);
22450
+ if (data.value_format) {
22451
+ const valueFormat = data.value_format.trim();
22452
+ // if it is a known validator, we don't need to validate it further
22453
+ if (KNOWN_VALIDATORS.has(valueFormat)) {
22454
+ return;
22455
+ }
22456
+ // otherwise, we need to validate it as a regex
22457
+ try {
22458
+ XRegExp(valueFormat);
22459
+ } catch (error) {
22460
+ ctx.addIssue({
22461
+ code: zod.ZodIssueCode.custom,
22462
+ message: getErrorMessage(error)
22463
+ });
22502
22464
  }
22503
- return this;
22504
22465
  }
22505
- has(key) {
22506
- return this.cache.has(key) || this.oldCache.has(key);
22466
+ }));
22467
+
22468
+ /**
22469
+ * @file Schema for redirect data.
22470
+ */
22471
+ /**
22472
+ * Zod schema for redirect data.
22473
+ */
22474
+ zodToCamelCase(baseCompatibilityDataSchema.extend({
22475
+ /**
22476
+ * Whether the redirect is blocking.
22477
+ */
22478
+ is_blocking: booleanSchema.default(false),
22479
+ /**
22480
+ * Resource type(s) belonging to the redirect.
22481
+ *
22482
+ * @see {@link https://developer.chrome.com/docs/extensions/reference/declarativeNetRequest/#type-ResourceType}
22483
+ */
22484
+ resource_types: zod.array(resourceTypeSchema).default([])
22485
+ }).superRefine(baseRefineLogic));
22486
+
22487
+ /**
22488
+ * @file Schema for scriptlet data.
22489
+ */
22490
+ /**
22491
+ * Zod schema for scriptlet parameter data.
22492
+ */
22493
+ const scriptletParameterSchema = zod.object({
22494
+ /**
22495
+ * Name of the actual parameter.
22496
+ */
22497
+ name: nonEmptyStringSchema,
22498
+ /**
22499
+ * Describes whether the parameter is required. Empty parameters are not allowed.
22500
+ */
22501
+ required: booleanSchema,
22502
+ /**
22503
+ * Short description of the parameter.
22504
+ * If not specified or it's value is `null`,then the description is not available.
22505
+ */
22506
+ description: nonEmptyStringSchema.nullable().default(null),
22507
+ /**
22508
+ * Regular expression that matches the value of the parameter.
22509
+ * If it's value is `null`, then the parameter value is not checked.
22510
+ */
22511
+ pattern: nonEmptyStringSchema.nullable().default(null),
22512
+ /**
22513
+ * Default value of the parameter (if any).
22514
+ */
22515
+ default: nonEmptyStringSchema.nullable().default(null),
22516
+ /**
22517
+ * Describes whether the parameter is used only for debugging purposes.
22518
+ */
22519
+ debug: booleanSchema.default(false)
22520
+ });
22521
+ /**
22522
+ * Zod schema for scriptlet parameters.
22523
+ */
22524
+ const scriptletParametersSchema = zod.array(scriptletParameterSchema);
22525
+ /**
22526
+ * Zod schema for scriptlet data.
22527
+ */
22528
+ zodToCamelCase(baseCompatibilityDataSchema.extend({
22529
+ /**
22530
+ * List of parameters that the scriptlet accepts.
22531
+ * **Every** parameter should be listed here, because we check that the scriptlet is used correctly
22532
+ * (e.g. that the number of parameters is correct).
22533
+ */
22534
+ parameters: scriptletParametersSchema.optional()
22535
+ }).superRefine((data, ctx) => {
22536
+ // TODO: find something better, for now we can't add refine logic to the base schema:
22537
+ // https://github.com/colinhacks/zod/issues/454#issuecomment-848370721
22538
+ baseRefineLogic(data, ctx);
22539
+ // we don't allow required parameters after optional ones
22540
+ if (!data.parameters) {
22541
+ return;
22507
22542
  }
22508
- peek(key) {
22509
- if (this.cache.has(key)) {
22510
- return this.cache.get(key);
22511
- }
22512
- if (this.oldCache.has(key)) {
22513
- return this.oldCache.get(key);
22543
+ let optionalFound = false;
22544
+ for (const parameter of data.parameters) {
22545
+ if (optionalFound && parameter.required) {
22546
+ ctx.addIssue({
22547
+ code: zod.ZodIssueCode.custom,
22548
+ message: 'Required parameters must be before optional ones'
22549
+ });
22514
22550
  }
22515
- }
22516
- delete(key) {
22517
- const deleted = this.cache.delete(key);
22518
- if (deleted) {
22519
- this._size--;
22551
+ if (!parameter.required) {
22552
+ optionalFound = true;
22520
22553
  }
22521
- return this.oldCache.delete(key) || deleted;
22522
- }
22523
- clear() {
22524
- this.cache.clear();
22525
- this.oldCache.clear();
22526
- this._size = 0;
22527
22554
  }
22528
- *keys() {
22529
- for (const [key] of this) {
22530
- yield key;
22555
+ }));
22556
+
22557
+ /**
22558
+ * @file Scriptlet injection rule converter
22559
+ */
22560
+ const ABP_SCRIPTLET_PREFIX = 'abp-';
22561
+ const UBO_SCRIPTLET_PREFIX = 'ubo-';
22562
+ const UBO_SCRIPTLET_PREFIX_LENGTH = UBO_SCRIPTLET_PREFIX.length;
22563
+ const UBO_SCRIPTLET_JS_SUFFIX = '.js';
22564
+ const UBO_SCRIPTLET_JS_SUFFIX_LENGTH = UBO_SCRIPTLET_JS_SUFFIX.length;
22565
+ const COMMA_SEPARATOR = ',';
22566
+ const ADG_SET_CONSTANT_NAME = 'set-constant';
22567
+ const ADG_SET_CONSTANT_EMPTY_STRING = '';
22568
+ const ADG_SET_CONSTANT_EMPTY_ARRAY = 'emptyArr';
22569
+ const ADG_SET_CONSTANT_EMPTY_OBJECT = 'emptyObj';
22570
+ const UBO_SET_CONSTANT_EMPTY_STRING = '\'\'';
22571
+ const UBO_SET_CONSTANT_EMPTY_ARRAY = '[]';
22572
+ const UBO_SET_CONSTANT_EMPTY_OBJECT = '{}';
22573
+ const ADG_PREVENT_FETCH_NAME = 'prevent-fetch';
22574
+ const ADG_PREVENT_FETCH_EMPTY_STRING = '';
22575
+ const ADG_PREVENT_FETCH_WILDCARD = '*';
22576
+ const UBO_NO_FETCH_IF_WILDCARD = '/^/';
22577
+ const UBO_REMOVE_CLASS_NAME = 'remove-class.js';
22578
+ const UBO_REMOVE_ATTR_NAME = 'remove-attr.js';
22579
+ const setConstantAdgToUboMap = {
22580
+ [ADG_SET_CONSTANT_EMPTY_STRING]: UBO_SET_CONSTANT_EMPTY_STRING,
22581
+ [ADG_SET_CONSTANT_EMPTY_ARRAY]: UBO_SET_CONSTANT_EMPTY_ARRAY,
22582
+ [ADG_SET_CONSTANT_EMPTY_OBJECT]: UBO_SET_CONSTANT_EMPTY_OBJECT
22583
+ };
22584
+ const REMOVE_ATTR_CLASS_APPLYING = new Set(['asap', 'stay', 'complete']);
22585
+ /**
22586
+ * Scriptlet injection rule converter class
22587
+ *
22588
+ * @todo Implement `convertToUbo` and `convertToAbp`
22589
+ */
22590
+ class ScriptletRuleConverter extends RuleConverterBase {
22591
+ /**
22592
+ * Converts a scriptlet injection rule to AdGuard format, if possible.
22593
+ *
22594
+ * @param rule Rule node to convert
22595
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
22596
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
22597
+ * If the rule was not converted, the result array will contain the original node with the same object reference
22598
+ * @throws If the rule is invalid or cannot be converted
22599
+ */
22600
+ static convertToAdg(rule) {
22601
+ // Ignore AdGuard rules
22602
+ if (rule.syntax === AdblockSyntax.Adg) {
22603
+ return createNodeConversionResult([rule], false);
22531
22604
  }
22532
- }
22533
- *values() {
22534
- for (const [, value] of this) {
22535
- yield value;
22605
+ const separator = rule.separator.value;
22606
+ let convertedSeparator = separator;
22607
+ convertedSeparator = rule.exception ? CosmeticRuleSeparator.AdgJsInjectionException : CosmeticRuleSeparator.AdgJsInjection;
22608
+ const convertedScriptlets = [];
22609
+ // Special case: empty uBO exception scriptlet, e.g. `example.com#@#+js()`
22610
+ if (rule.syntax === AdblockSyntax.Ubo && rule.body.children.length === 1 && rule.body.children[0].children.length === 0) {
22611
+ convertedScriptlets.push(rule.body.children[0]);
22612
+ } else {
22613
+ for (const scriptlet of rule.body.children) {
22614
+ // Clone the node to avoid any side effects
22615
+ const scriptletClone = cloneScriptletRuleNode(scriptlet);
22616
+ // Remove possible quotes just to make it easier to work with the scriptlet name
22617
+ const scriptletName = QuoteUtils.setStringQuoteType(getScriptletName(scriptletClone), QuoteType.None);
22618
+ // Add prefix if it's not already there
22619
+ let prefix;
22620
+ // In uBO / ABP syntax, if a parameter contains the separator character, it should be escaped,
22621
+ // but during the conversion, we need to unescape them, because AdGuard syntax uses quotes to
22622
+ // distinguish between parameters.
22623
+ let charToUnescape;
22624
+ switch (rule.syntax) {
22625
+ case AdblockSyntax.Abp:
22626
+ prefix = ABP_SCRIPTLET_PREFIX;
22627
+ charToUnescape = SPACE;
22628
+ break;
22629
+ case AdblockSyntax.Ubo:
22630
+ prefix = UBO_SCRIPTLET_PREFIX;
22631
+ charToUnescape = COMMA_SEPARATOR;
22632
+ break;
22633
+ default:
22634
+ prefix = EMPTY;
22635
+ }
22636
+ if (!scriptletName.startsWith(prefix)) {
22637
+ setScriptletName(scriptletClone, `${prefix}${scriptletName}`);
22638
+ }
22639
+ if (!isUndefined(charToUnescape)) {
22640
+ transformAllScriptletArguments(scriptletClone, value => {
22641
+ if (!isNull(value)) {
22642
+ return QuoteUtils.unescapeSingleEscapedOccurrences(value, charToUnescape);
22643
+ }
22644
+ return value;
22645
+ });
22646
+ }
22647
+ if (rule.syntax === AdblockSyntax.Ubo) {
22648
+ const scriptletData = scriptletsCompatibilityTable.getFirst(scriptletName, GenericPlatform.UboAny);
22649
+ // Some scriptlets have special values that need to be converted
22650
+ if (scriptletData && (scriptletData.name === UBO_REMOVE_CLASS_NAME || scriptletData.name === UBO_REMOVE_ATTR_NAME) && scriptletClone.children.length > 2) {
22651
+ const selectors = [];
22652
+ let applying = null;
22653
+ let lastArg = scriptletClone.children.pop();
22654
+ // The very last argument might be the 'applying' parameter
22655
+ if (lastArg) {
22656
+ if (REMOVE_ATTR_CLASS_APPLYING.has(lastArg.value)) {
22657
+ applying = lastArg.value;
22658
+ } else {
22659
+ selectors.push(lastArg.value);
22660
+ }
22661
+ }
22662
+ while (scriptletClone.children.length > 2) {
22663
+ lastArg = scriptletClone.children.pop();
22664
+ if (lastArg) {
22665
+ selectors.push(lastArg.value.trim());
22666
+ }
22667
+ }
22668
+ // Set last arg to be the combined selectors (in reverse order, because we popped them)
22669
+ if (selectors.length > 0) {
22670
+ scriptletClone.children.push({
22671
+ type: 'Value',
22672
+ value: selectors.reverse().join(', ')
22673
+ });
22674
+ }
22675
+ // Push back the 'applying' parameter if it was found previously
22676
+ if (!isNull(applying)) {
22677
+ // If we don't have any selectors,
22678
+ // we need to add an empty parameter before the 'applying' one
22679
+ if (selectors.length === 0) {
22680
+ scriptletClone.children.push({
22681
+ type: 'Value',
22682
+ value: EMPTY
22683
+ });
22684
+ }
22685
+ scriptletClone.children.push({
22686
+ type: 'Value',
22687
+ value: applying
22688
+ });
22689
+ }
22690
+ }
22691
+ }
22692
+ // ADG scriptlet parameters should be quoted, and single quoted are preferred
22693
+ setScriptletQuoteType(scriptletClone, QuoteType.Single);
22694
+ convertedScriptlets.push(scriptletClone);
22695
+ }
22536
22696
  }
22697
+ return createNodeConversionResult(convertedScriptlets.map(scriptlet => {
22698
+ const res = {
22699
+ category: rule.category,
22700
+ type: rule.type,
22701
+ syntax: AdblockSyntax.Adg,
22702
+ exception: rule.exception,
22703
+ domains: cloneDomainListNode(rule.domains),
22704
+ separator: {
22705
+ type: 'Value',
22706
+ value: convertedSeparator
22707
+ },
22708
+ body: {
22709
+ type: rule.body.type,
22710
+ children: [scriptlet]
22711
+ }
22712
+ };
22713
+ if (rule.modifiers) {
22714
+ res.modifiers = cloneModifierListNode(rule.modifiers);
22715
+ }
22716
+ return res;
22717
+ }), true);
22537
22718
  }
22538
- *[Symbol.iterator]() {
22539
- for (const item of this.cache) {
22540
- yield item;
22719
+ /**
22720
+ * Converts a scriptlet injection rule to uBlock format, if possible.
22721
+ *
22722
+ * @param rule Rule node to convert
22723
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
22724
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
22725
+ * If the rule was not converted, the result array will contain the original node with the same object reference
22726
+ * @throws If the rule is invalid or cannot be converted
22727
+ */
22728
+ static convertToUbo(rule) {
22729
+ // Ignore uBlock rules
22730
+ if (rule.syntax === AdblockSyntax.Ubo) {
22731
+ return createNodeConversionResult([rule], false);
22541
22732
  }
22542
- for (const item of this.oldCache) {
22543
- const [key] = item;
22544
- if (!this.cache.has(key)) {
22545
- yield item;
22733
+ const separator = rule.separator.value;
22734
+ let convertedSeparator = separator;
22735
+ convertedSeparator = rule.exception ? CosmeticRuleSeparator.ElementHidingException : CosmeticRuleSeparator.ElementHiding;
22736
+ const convertedScriptlets = [];
22737
+ // Special case: empty AdGuard exception scriptlet, e.g. `example.com#%#//scriptlet()`
22738
+ if (rule.syntax === AdblockSyntax.Adg && rule.body.children.length === 1 && rule.body.children[0].children.length === 0) {
22739
+ convertedScriptlets.push(rule.body.children[0]);
22740
+ } else {
22741
+ for (const scriptlet of rule.body.children) {
22742
+ // Clone the node to avoid any side effects
22743
+ const scriptletClone = cloneScriptletRuleNode(scriptlet);
22744
+ // Remove possible quotes just to make it easier to work with the scriptlet name
22745
+ const scriptletName = QuoteUtils.setStringQuoteType(getScriptletName(scriptletClone), QuoteType.None);
22746
+ let uboScriptletName;
22747
+ if (rule.syntax === AdblockSyntax.Adg && scriptletName.startsWith(UBO_SCRIPTLET_PREFIX)) {
22748
+ // Special case: AdGuard syntax 'preserves' the original scriptlet name,
22749
+ // so we need to convert it back by removing the uBO prefix
22750
+ uboScriptletName = scriptletName.slice(UBO_SCRIPTLET_PREFIX_LENGTH);
22751
+ } else {
22752
+ // Otherwise, try to find the corresponding uBO scriptlet name, or use the original one if not found
22753
+ const uboScriptlet = scriptletsCompatibilityTable.getFirst(scriptletName, GenericPlatform.UboAny);
22754
+ uboScriptletName = uboScriptlet?.name ?? scriptletName;
22755
+ }
22756
+ // Remove the '.js' suffix if it's there - its presence is not mandatory
22757
+ if (uboScriptletName.endsWith(UBO_SCRIPTLET_JS_SUFFIX)) {
22758
+ uboScriptletName = uboScriptletName.slice(0, -UBO_SCRIPTLET_JS_SUFFIX_LENGTH);
22759
+ }
22760
+ setScriptletName(scriptletClone, uboScriptletName);
22761
+ setScriptletQuoteType(scriptletClone, QuoteType.None);
22762
+ // Escape unescaped commas in parameters, because uBlock Origin uses them as separators.
22763
+ // For example, the following AdGuard rule:
22764
+ //
22765
+ // example.com#%#//scriptlet('spoof-css', '.adsbygoogle, #ads', 'visibility', 'visible')
22766
+ //
22767
+ // ↓↓ should be converted to ↓↓
22768
+ //
22769
+ // example.com##+js(spoof-css.js, .adsbygoogle\, #ads, visibility, visible)
22770
+ // ------------ ------------------- ---------- -------
22771
+ // arg 0 arg 1 arg 2 arg 3
22772
+ //
22773
+ // and we need to escape the comma in the second argument to prevent it from being treated
22774
+ // as two separate arguments.
22775
+ transformAllScriptletArguments(scriptletClone, value => {
22776
+ if (!isNull(value)) {
22777
+ return QuoteUtils.escapeUnescapedOccurrences(value, COMMA_SEPARATOR);
22778
+ }
22779
+ return value;
22780
+ });
22781
+ // Unescape spaces in parameters, because uBlock Origin doesn't treat them as separators.
22782
+ if (rule.syntax === AdblockSyntax.Abp) {
22783
+ transformAllScriptletArguments(scriptletClone, value => {
22784
+ if (!isNull(value)) {
22785
+ return QuoteUtils.unescapeSingleEscapedOccurrences(value, SPACE);
22786
+ }
22787
+ return value;
22788
+ });
22789
+ }
22790
+ // Some scriptlets have special values that need to be converted
22791
+ switch (scriptletName) {
22792
+ case ADG_SET_CONSTANT_NAME:
22793
+ transformNthScriptletArgument(scriptletClone, 2, value => {
22794
+ if (!isNull(value)) {
22795
+ return setConstantAdgToUboMap[value] ?? value;
22796
+ }
22797
+ return value;
22798
+ });
22799
+ break;
22800
+ case ADG_PREVENT_FETCH_NAME:
22801
+ transformNthScriptletArgument(scriptletClone, 1, value => {
22802
+ if (value === ADG_PREVENT_FETCH_EMPTY_STRING || value === ADG_PREVENT_FETCH_WILDCARD) {
22803
+ return UBO_NO_FETCH_IF_WILDCARD;
22804
+ }
22805
+ return value;
22806
+ });
22807
+ break;
22808
+ }
22809
+ convertedScriptlets.push(scriptletClone);
22546
22810
  }
22547
22811
  }
22548
- }
22549
- get size() {
22550
- let oldCacheSize = 0;
22551
- for (const key of this.oldCache.keys()) {
22552
- if (!this.cache.has(key)) {
22553
- oldCacheSize++;
22812
+ return createNodeConversionResult(convertedScriptlets.map(scriptlet => {
22813
+ const res = {
22814
+ category: rule.category,
22815
+ type: rule.type,
22816
+ syntax: AdblockSyntax.Ubo,
22817
+ exception: rule.exception,
22818
+ domains: cloneDomainListNode(rule.domains),
22819
+ separator: {
22820
+ type: 'Value',
22821
+ value: convertedSeparator
22822
+ },
22823
+ body: {
22824
+ type: rule.body.type,
22825
+ children: [scriptlet]
22826
+ }
22827
+ };
22828
+ if (rule.modifiers) {
22829
+ res.modifiers = cloneModifierListNode(rule.modifiers);
22554
22830
  }
22555
- }
22556
- return Math.min(this._size + oldCacheSize, this.maxSize);
22831
+ return res;
22832
+ }), true);
22557
22833
  }
22558
22834
  }
22559
- var quickLru = QuickLRU;
22560
- const mapObj = mapObjExports;
22561
- const camelCase = camelcaseExports;
22562
- const QuickLru = quickLru;
22563
- const has = (array, key) => array.some(x => {
22564
- if (typeof x === 'string') {
22565
- return x === key;
22566
- }
22567
- x.lastIndex = 0;
22568
- return x.test(key);
22569
- });
22570
- const cache = new QuickLru({
22571
- maxSize: 100000
22572
- });
22573
22835
 
22574
- // Reproduces behavior from `map-obj`
22575
- const isObject = value => typeof value === 'object' && value !== null && !(value instanceof RegExp) && !(value instanceof Error) && !(value instanceof Date);
22576
- const camelCaseConvert = (input, options) => {
22577
- if (!isObject(input)) {
22578
- return input;
22836
+ /**
22837
+ * @file Utility functions for working with modifier nodes
22838
+ */
22839
+ /**
22840
+ * Creates a modifier node
22841
+ *
22842
+ * @param name Name of the modifier
22843
+ * @param value Value of the modifier
22844
+ * @param exception Whether the modifier is an exception
22845
+ * @returns Modifier node
22846
+ */
22847
+ function createModifierNode(name, value = undefined, exception = false) {
22848
+ const result = {
22849
+ type: 'Modifier',
22850
+ exception,
22851
+ name: {
22852
+ type: 'Value',
22853
+ value: name
22854
+ }
22855
+ };
22856
+ if (!isUndefined(value)) {
22857
+ result.value = {
22858
+ type: 'Value',
22859
+ value
22860
+ };
22579
22861
  }
22580
- options = {
22581
- deep: false,
22582
- pascalCase: false,
22583
- ...options
22862
+ return result;
22863
+ }
22864
+ /**
22865
+ * Creates a modifier list node
22866
+ *
22867
+ * @param modifiers Modifiers to put in the list (optional, defaults to an empty list)
22868
+ * @returns Modifier list node
22869
+ */
22870
+ function createModifierListNode(modifiers = []) {
22871
+ const result = {
22872
+ type: 'ModifierList',
22873
+ // We need to clone the modifiers to avoid side effects
22874
+ children: modifiers.length ? clone(modifiers) : []
22584
22875
  };
22585
- const {
22586
- exclude,
22587
- pascalCase,
22588
- stopPaths,
22589
- deep
22590
- } = options;
22591
- const stopPathsSet = new Set(stopPaths);
22592
- const makeMapper = parentPath => (key, value) => {
22593
- if (deep && isObject(value)) {
22594
- const path = parentPath === undefined ? key : `${parentPath}.${key}`;
22595
- if (!stopPathsSet.has(path)) {
22596
- value = mapObj(value, makeMapper(path));
22876
+ return result;
22877
+ }
22878
+
22879
+ /**
22880
+ * A very simple map extension that allows to store multiple values for the same key
22881
+ * by storing them in an array.
22882
+ *
22883
+ * @todo Add more methods if needed
22884
+ */
22885
+ class MultiValueMap extends Map {
22886
+ /**
22887
+ * Adds a value to the map. If the key already exists, the value will be appended to the existing array,
22888
+ * otherwise a new array will be created for the key.
22889
+ *
22890
+ * @param key Key to add
22891
+ * @param values Value(s) to add
22892
+ */
22893
+ add(key, ...values) {
22894
+ let currentValues = super.get(key);
22895
+ if (isUndefined(currentValues)) {
22896
+ currentValues = [];
22897
+ super.set(key, values);
22898
+ }
22899
+ currentValues.push(...values);
22900
+ }
22901
+ }
22902
+
22903
+ /**
22904
+ * @file Cosmetic rule modifier converter from uBO to ADG
22905
+ */
22906
+ const UBO_MATCHES_PATH_OPERATOR = 'matches-path';
22907
+ const ADG_PATH_MODIFIER = 'path';
22908
+ /**
22909
+ * Special characters in modifier regexps that should be escaped
22910
+ */
22911
+ const SPECIAL_MODIFIER_REGEX_CHARS = new Set([OPEN_SQUARE_BRACKET, CLOSE_SQUARE_BRACKET, COMMA, ESCAPE_CHARACTER]);
22912
+ /**
22913
+ * Helper class for converting cosmetic rule modifiers from uBO to ADG
22914
+ */
22915
+ class AdgCosmeticRuleModifierConverter {
22916
+ /**
22917
+ * Converts a uBO cosmetic rule modifier list to ADG, if possible.
22918
+ *
22919
+ * @param modifierList Cosmetic rule modifier list node to convert
22920
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
22921
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
22922
+ * If the node was not converted, the result will contain the original node with the same object reference
22923
+ * @throws If the modifier list cannot be converted
22924
+ * @see {@link https://github.com/gorhill/uBlock/wiki/Procedural-cosmetic-filters#cosmetic-filter-operators}
22925
+ */
22926
+ static convertFromUbo(modifierList) {
22927
+ const conversionMap = new MultiValueMap();
22928
+ modifierList.children.forEach((modifier, index) => {
22929
+ // :matches-path
22930
+ if (modifier.name.value === UBO_MATCHES_PATH_OPERATOR) {
22931
+ if (!modifier.value) {
22932
+ throw new RuleConversionError(`'${UBO_MATCHES_PATH_OPERATOR}' operator requires a value`);
22933
+ }
22934
+ const value = RegExpUtils.isRegexPattern(modifier.value.value) ? StringUtils.escapeCharacters(modifier.value.value, SPECIAL_MODIFIER_REGEX_CHARS) : modifier.value.value;
22935
+ // Convert uBO's `:matches-path(...)` operator to ADG's `$path=...` modifier
22936
+ conversionMap.add(index, createModifierNode(ADG_PATH_MODIFIER,
22937
+ // We should negate the regexp if the modifier is an exception
22938
+ modifier.exception
22939
+ // eslint-disable-next-line max-len
22940
+ ? `${REGEX_MARKER}${RegExpUtils.negateRegexPattern(RegExpUtils.patternToRegexp(value))}${REGEX_MARKER}` : value));
22597
22941
  }
22942
+ });
22943
+ // Check if we have any converted modifiers
22944
+ if (conversionMap.size) {
22945
+ const modifierListClone = clone(modifierList);
22946
+ // Replace the original modifiers with the converted ones
22947
+ modifierListClone.children = modifierListClone.children.map((modifier, index) => {
22948
+ const convertedModifier = conversionMap.get(index);
22949
+ return convertedModifier ?? modifier;
22950
+ }).flat();
22951
+ return createConversionResult(modifierListClone, true);
22598
22952
  }
22599
- if (!(exclude && has(exclude, key))) {
22600
- const cacheKey = pascalCase ? `${key}_` : key;
22601
- if (cache.has(cacheKey)) {
22602
- key = cache.get(cacheKey);
22603
- } else {
22604
- const returnValue = camelCase(key, {
22605
- pascalCase,
22606
- locale: false
22953
+ // Otherwise, just return the original modifier list
22954
+ return createConversionResult(modifierList, false);
22955
+ }
22956
+ }
22957
+ const ERROR_MESSAGES$1 = {
22958
+ // eslint-disable-next-line max-len
22959
+ INVALID_ATTRIBUTE_VALUE: `Expected '${getFormattedTokenName(TokenType$1.Ident)}' or '${getFormattedTokenName(TokenType$1.String)}' as attribute value, but got '%s' with value '%s`
22960
+ };
22961
+ var PseudoClasses;
22962
+ (function (PseudoClasses) {
22963
+ PseudoClasses["AbpContains"] = "-abp-contains";
22964
+ PseudoClasses["AbpHas"] = "-abp-has";
22965
+ PseudoClasses["Contains"] = "contains";
22966
+ PseudoClasses["Has"] = "has";
22967
+ PseudoClasses["HasText"] = "has-text";
22968
+ PseudoClasses["MatchesCss"] = "matches-css";
22969
+ PseudoClasses["MatchesCssAfter"] = "matches-css-after";
22970
+ PseudoClasses["MatchesCssBefore"] = "matches-css-before";
22971
+ PseudoClasses["Not"] = "not";
22972
+ })(PseudoClasses || (PseudoClasses = {}));
22973
+ var PseudoElements;
22974
+ (function (PseudoElements) {
22975
+ PseudoElements["After"] = "after";
22976
+ PseudoElements["Before"] = "before";
22977
+ })(PseudoElements || (PseudoElements = {}));
22978
+ const PSEUDO_ELEMENT_NAMES = new Set([PseudoElements.After, PseudoElements.Before]);
22979
+ /**
22980
+ * CSS selector converter
22981
+ *
22982
+ * @todo Implement `convertToUbo` and `convertToAbp`
22983
+ */
22984
+ class CssSelectorConverter extends ConverterBase {
22985
+ /**
22986
+ * Converts Extended CSS elements to AdGuard-compatible ones
22987
+ *
22988
+ * @param selectorList Selector list to convert
22989
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
22990
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
22991
+ * If the node was not converted, the result will contain the original node with the same object reference
22992
+ * @throws If the rule is invalid or incompatible
22993
+ */
22994
+ static convertToAdg(selectorList) {
22995
+ const stream = selectorList instanceof CssTokenStream ? selectorList : new CssTokenStream(selectorList);
22996
+ const converted = [];
22997
+ const convertAndPushPseudo = pseudo => {
22998
+ switch (pseudo) {
22999
+ case PseudoClasses.AbpContains:
23000
+ case PseudoClasses.HasText:
23001
+ converted.push(PseudoClasses.Contains);
23002
+ converted.push(OPEN_PARENTHESIS);
23003
+ break;
23004
+ case PseudoClasses.AbpHas:
23005
+ converted.push(PseudoClasses.Has);
23006
+ converted.push(OPEN_PARENTHESIS);
23007
+ break;
23008
+ // a bit special case:
23009
+ // - `:matches-css-before(...)` → `:matches-css(before, ...)`
23010
+ // - `:matches-css-after(...)` → `:matches-css(after, ...)`
23011
+ case PseudoClasses.MatchesCssBefore:
23012
+ case PseudoClasses.MatchesCssAfter:
23013
+ converted.push(PseudoClasses.MatchesCss);
23014
+ converted.push(OPEN_PARENTHESIS);
23015
+ converted.push(pseudo.substring(PseudoClasses.MatchesCss.length + 1));
23016
+ converted.push(COMMA);
23017
+ break;
23018
+ default:
23019
+ converted.push(pseudo);
23020
+ converted.push(OPEN_PARENTHESIS);
23021
+ break;
23022
+ }
23023
+ };
23024
+ while (!stream.isEof()) {
23025
+ const token = stream.getOrFail();
23026
+ if (token.type === TokenType$1.Colon) {
23027
+ // Advance colon
23028
+ stream.advance();
23029
+ converted.push(COLON);
23030
+ const tempToken = stream.getOrFail();
23031
+ // Double colon is a pseudo-element
23032
+ if (tempToken.type === TokenType$1.Colon) {
23033
+ stream.advance();
23034
+ converted.push(COLON);
23035
+ continue;
23036
+ }
23037
+ if (tempToken.type === TokenType$1.Ident) {
23038
+ const name = stream.source.slice(tempToken.start, tempToken.end);
23039
+ if (PSEUDO_ELEMENT_NAMES.has(name)) {
23040
+ // Add an extra colon to the name
23041
+ converted.push(COLON);
23042
+ converted.push(name);
23043
+ } else {
23044
+ // Add the name as is
23045
+ converted.push(name);
23046
+ }
23047
+ // Advance the names
23048
+ stream.advance();
23049
+ } else if (tempToken.type === TokenType$1.Function) {
23050
+ const name = stream.source.slice(tempToken.start, tempToken.end - 1); // omit the last parenthesis
23051
+ // :-abp-contains(...) → :contains(...)
23052
+ // :has-text(...) → :contains(...)
23053
+ // :-abp-has(...) → :has(...)
23054
+ convertAndPushPseudo(name);
23055
+ // Advance the function name
23056
+ stream.advance();
23057
+ }
23058
+ } else if (token.type === TokenType$1.OpenSquareBracket) {
23059
+ let tempToken;
23060
+ const {
23061
+ start
23062
+ } = token;
23063
+ stream.advance();
23064
+ // Converts legacy Extended CSS selectors to the modern Extended CSS syntax.
23065
+ // For example:
23066
+ // - `[-ext-has=...]` → `:has(...)`
23067
+ // - `[-ext-contains=...]` → `:contains(...)`
23068
+ // - `[-ext-matches-css-before=...]` → `:matches-css(before, ...)`
23069
+ stream.skipWhitespace();
23070
+ stream.expect(TokenType$1.Ident);
23071
+ tempToken = stream.getOrFail();
23072
+ let attr = stream.source.slice(tempToken.start, tempToken.end);
23073
+ // Skip if the attribute name is not a legacy Extended CSS one
23074
+ if (!(attr.startsWith(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX) || attr.startsWith(ABP_EXT_CSS_PREFIX))) {
23075
+ converted.push(stream.source.slice(start, tempToken.end));
23076
+ stream.advance();
23077
+ continue;
23078
+ }
23079
+ if (attr.startsWith(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX)) {
23080
+ attr = attr.slice(LEGACY_EXT_CSS_ATTRIBUTE_PREFIX.length);
23081
+ }
23082
+ stream.advance();
23083
+ stream.skipWhitespace();
23084
+ // Next token should be an equality operator (=), because Extended CSS attribute selectors
23085
+ // do not support other operators
23086
+ stream.expect(TokenType$1.Delim, {
23087
+ value: EQUALS
22607
23088
  });
22608
- if (key.length < 100) {
22609
- // Prevent abuse
22610
- cache.set(cacheKey, returnValue);
23089
+ stream.advance();
23090
+ // Skip optional whitespace after the operator
23091
+ stream.skipWhitespace();
23092
+ // Parse attribute value
23093
+ tempToken = stream.getOrFail();
23094
+ // According to the spec, attribute value should be an identifier or a string
23095
+ if (tempToken.type !== TokenType$1.Ident && tempToken.type !== TokenType$1.String) {
23096
+ throw new Error(sprintf(ERROR_MESSAGES$1.INVALID_ATTRIBUTE_VALUE, getFormattedTokenName(tempToken.type), stream.source.slice(tempToken.start, tempToken.end)));
22611
23097
  }
22612
- key = returnValue;
23098
+ const value = stream.source.slice(tempToken.start, tempToken.end);
23099
+ // Advance the attribute value
23100
+ stream.advance();
23101
+ // Skip optional whitespace after the attribute value
23102
+ stream.skipWhitespace();
23103
+ // Next character should be a closing square bracket
23104
+ // We don't allow flags for Extended CSS attribute selectors
23105
+ stream.expect(TokenType$1.CloseSquareBracket);
23106
+ stream.advance();
23107
+ converted.push(COLON);
23108
+ convertAndPushPseudo(attr);
23109
+ let processedValue = value.slice(1, -1); // omit the quotes
23110
+ if (attr === PseudoClasses.Has) {
23111
+ // TODO: Optimize this to avoid double tokenization
23112
+ processedValue = CssSelectorConverter.convertToAdg(processedValue).result;
23113
+ }
23114
+ converted.push(processedValue);
23115
+ converted.push(CLOSE_PARENTHESIS);
23116
+ } else {
23117
+ converted.push(stream.source.slice(token.start, token.end));
23118
+ // Advance the token
23119
+ stream.advance();
22613
23120
  }
22614
23121
  }
22615
- return [key, value];
22616
- };
22617
- return mapObj(input, makeMapper(undefined));
22618
- };
22619
- var camelcaseKeys = (input, options) => {
22620
- if (Array.isArray(input)) {
22621
- return Object.keys(input).map(key => camelCaseConvert(input[key], options));
23122
+ const convertedSelectorList = converted.join(EMPTY);
23123
+ return createConversionResult(convertedSelectorList, stream.source !== convertedSelectorList);
22622
23124
  }
22623
- return camelCaseConvert(input, options);
22624
- };
22625
- var camelCaseKeys = /*@__PURE__*/getDefaultExportFromCjs(camelcaseKeys);
23125
+ }
22626
23126
 
22627
23127
  /**
22628
- * @file Zod camelCase utility.
23128
+ * @file CSS injection rule converter
22629
23129
  */
22630
- // eslint-disable-next-line import/no-extraneous-dependencies
22631
23130
  /**
22632
- * Transforms Zod schema to camelCase.
22633
- *
22634
- * @param zod Zod schema.
22635
- *
22636
- * @returns Zod schema with camelCase properties.
23131
+ * CSS injection rule converter class
22637
23132
  *
22638
- * @see {@link https://github.com/colinhacks/zod/issues/486#issuecomment-1501097361}
22639
- */
22640
- const zodToCamelCase = zod => {
22641
- return zod.transform(val => camelCaseKeys(val));
22642
- };
22643
-
22644
- /**
22645
- * @file Base compatibility data schema, which is commonly used in compatibility tables.
22646
- */
22647
- /**
22648
- * Zod schema for boolean values. Accepts both boolean and string values.
22649
- */
22650
- const booleanSchema = zod.union([zod.string().transform(val => val.trim().toLowerCase() === 'true'), zod.boolean()]);
22651
- /**
22652
- * Zod schema for non-empty string values.
22653
- */
22654
- const nonEmptyStringSchema = zod.string().transform(val => val.trim()).pipe(zod.string().min(1));
22655
- /**
22656
- * Zod schema for base compatibility data.
22657
- * Here we use snake_case properties because the compatibility data is stored in YAML files.
23133
+ * @todo Implement `convertToUbo` and `convertToAbp`
22658
23134
  */
22659
- const baseCompatibilityDataSchema = zod.object({
22660
- /**
22661
- * Name of the actual entity.
22662
- */
22663
- name: nonEmptyStringSchema,
22664
- /**
22665
- * List of aliases for the entity (if any).
22666
- */
22667
- aliases: zod.array(nonEmptyStringSchema).nullable().default(null),
22668
- /**
22669
- * Short description of the actual entity.
22670
- * If not specified or it's value is `null`, then the description is not available.
22671
- */
22672
- description: nonEmptyStringSchema.nullable().default(null),
22673
- /**
22674
- * Link to the documentation. If not specified or it's value is `null`, then the documentation is not available.
22675
- */
22676
- docs: nonEmptyStringSchema.nullable().default(null),
22677
- /**
22678
- * The version of the adblocker in which the entity was added.
22679
- * For AdGuard resources, the version of the library is specified.
22680
- */
22681
- version_added: nonEmptyStringSchema.nullable().default(null),
22682
- /**
22683
- * The version of the adblocker when the entity was removed.
22684
- */
22685
- version_removed: nonEmptyStringSchema.nullable().default(null),
22686
- /**
22687
- * Describes whether the entity is deprecated.
22688
- */
22689
- deprecated: booleanSchema.default(false),
22690
- /**
22691
- * Message that describes why the entity is deprecated.
22692
- * If not specified or it's value is `null`, then the message is not available.
22693
- * It's value is omitted if the entity is not marked as deprecated.
22694
- */
22695
- deprecation_message: nonEmptyStringSchema.nullable().default(null),
22696
- /**
22697
- * Describes whether the entity is removed; for *already removed* features.
22698
- */
22699
- removed: booleanSchema.default(false),
23135
+ class CssInjectionRuleConverter extends RuleConverterBase {
22700
23136
  /**
22701
- * Message that describes why the entity is removed.
22702
- * If not specified or it's value is `null`, then the message is not available.
22703
- * It's value is omitted if the entity is not marked as deprecated.
23137
+ * Converts a CSS injection rule to AdGuard format, if possible.
23138
+ *
23139
+ * @param rule Rule node to convert
23140
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
23141
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
23142
+ * If the rule was not converted, the result array will contain the original node with the same object reference
23143
+ * @throws If the rule is invalid or cannot be converted
22704
23144
  */
22705
- removal_message: nonEmptyStringSchema.nullable().default(null)
22706
- });
22707
- /**
22708
- * Zod schema for base compatibility data with camelCase properties.
22709
- */
22710
- zodToCamelCase(baseCompatibilityDataSchema);
22711
- /**
22712
- * Refinement logic for base compatibility data.
22713
- *
22714
- * @param data Base compatibility data.
22715
- * @param ctx Refinement context.
22716
- */
22717
- const baseRefineLogic = (data, ctx) => {
22718
- if (data.deprecated && !data.deprecation_message) {
22719
- ctx.addIssue({
22720
- code: zod.ZodIssueCode.custom,
22721
- message: 'deprecation_message is required for deprecated modifiers'
22722
- });
22723
- }
22724
- if (!data.deprecated && data.deprecation_message) {
22725
- ctx.addIssue({
22726
- code: zod.ZodIssueCode.custom,
22727
- message: 'deprecation_message is only allowed for deprecated modifiers'
22728
- });
22729
- }
22730
- if (data.aliases && data.aliases.length !== new Set(data.aliases).size) {
22731
- ctx.addIssue({
22732
- code: zod.ZodIssueCode.custom,
22733
- message: 'Aliases must be unique'
22734
- });
23145
+ static convertToAdg(rule) {
23146
+ const separator = rule.separator.value;
23147
+ let convertedSeparator = separator;
23148
+ const stream = new CssTokenStream(rule.body.selectorList.value);
23149
+ const convertedSelectorList = CssSelectorConverter.convertToAdg(stream);
23150
+ // Change the separator if the rule contains ExtendedCSS elements,
23151
+ // but do not force non-extended CSS separator if the rule does not contain any ExtendedCSS selectors,
23152
+ // because sometimes we use it to force executing ExtendedCSS library.
23153
+ if (stream.hasAnySelectorExtendedCssNodeStrict() || rule.body.remove) {
23154
+ convertedSeparator = rule.exception ? CosmeticRuleSeparator.AdgExtendedCssInjectionException : CosmeticRuleSeparator.AdgExtendedCssInjection;
23155
+ } else if (rule.syntax !== AdblockSyntax.Adg) {
23156
+ // If the original rule syntax is not AdGuard, use the default separator
23157
+ // e.g. if the input rule is from uBO, we need to convert ## to #$#.
23158
+ convertedSeparator = rule.exception ? CosmeticRuleSeparator.AdgCssInjectionException : CosmeticRuleSeparator.AdgCssInjection;
23159
+ }
23160
+ // Check if the rule needs to be converted
23161
+ if (!(rule.syntax === AdblockSyntax.Common || rule.syntax === AdblockSyntax.Adg) || separator !== convertedSeparator || convertedSelectorList.isConverted) {
23162
+ // TODO: Replace with custom clone method
23163
+ const ruleClone = clone(rule);
23164
+ ruleClone.syntax = AdblockSyntax.Adg;
23165
+ ruleClone.separator.value = convertedSeparator;
23166
+ ruleClone.body.selectorList.value = convertedSelectorList.result;
23167
+ return createNodeConversionResult([ruleClone], true);
23168
+ }
23169
+ // Otherwise, return the original rule
23170
+ return createNodeConversionResult([rule], false);
22735
23171
  }
22736
- };
23172
+ }
22737
23173
 
22738
23174
  /**
22739
- * Checks if error has message.
22740
- *
22741
- * @param error Error object.
22742
- * @returns If param is error.
23175
+ * @file Element hiding rule converter
22743
23176
  */
22744
- function isErrorWithMessage(error) {
22745
- return typeof error === 'object' && error !== null && 'message' in error && typeof error.message === 'string';
22746
- }
22747
23177
  /**
22748
- * Converts error to the error with message.
23178
+ * Element hiding rule converter class
22749
23179
  *
22750
- * @param maybeError Possible error.
22751
- * @returns Error with message.
23180
+ * @todo Implement `convertToUbo` and `convertToAbp`
22752
23181
  */
22753
- function toErrorWithMessage(maybeError) {
22754
- if (isErrorWithMessage(maybeError)) {
22755
- return maybeError;
22756
- }
22757
- try {
22758
- return new Error(JSON.stringify(maybeError));
22759
- } catch {
22760
- // fallback in case there's an error stringifying the maybeError
22761
- // like with circular references for example.
22762
- return new Error(String(maybeError));
23182
+ class ElementHidingRuleConverter extends RuleConverterBase {
23183
+ /**
23184
+ * Converts an element hiding rule to AdGuard format, if possible.
23185
+ *
23186
+ * @param rule Rule node to convert
23187
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
23188
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
23189
+ * If the rule was not converted, the result array will contain the original node with the same object reference
23190
+ * @throws If the rule is invalid or cannot be converted
23191
+ */
23192
+ static convertToAdg(rule) {
23193
+ const separator = rule.separator.value;
23194
+ let convertedSeparator = separator;
23195
+ const stream = new CssTokenStream(rule.body.selectorList.value);
23196
+ const convertedSelectorList = CssSelectorConverter.convertToAdg(stream);
23197
+ // Change the separator if the rule contains ExtendedCSS elements,
23198
+ // but do not force non-extended CSS separator if the rule does not contain any ExtendedCSS selectors,
23199
+ // because sometimes we use it to force executing ExtendedCSS library.
23200
+ if (stream.hasAnySelectorExtendedCssNodeStrict()) {
23201
+ convertedSeparator = rule.exception ? CosmeticRuleSeparator.ExtendedElementHidingException : CosmeticRuleSeparator.ExtendedElementHiding;
23202
+ }
23203
+ // Check if the rule needs to be converted
23204
+ if (!(rule.syntax === AdblockSyntax.Common || rule.syntax === AdblockSyntax.Adg) || separator !== convertedSeparator || convertedSelectorList.isConverted) {
23205
+ // TODO: Replace with custom clone method
23206
+ const ruleClone = clone(rule);
23207
+ ruleClone.syntax = AdblockSyntax.Adg;
23208
+ ruleClone.separator.value = convertedSeparator;
23209
+ ruleClone.body.selectorList.value = convertedSelectorList.result;
23210
+ return createNodeConversionResult([ruleClone], true);
23211
+ }
23212
+ // Otherwise, return the original rule
23213
+ return createNodeConversionResult([rule], false);
22763
23214
  }
22764
23215
  }
22765
- /**
22766
- * Converts error object to error with message. This method might be helpful to handle thrown errors.
22767
- *
22768
- * @param error Error object.
22769
- *
22770
- * @returns Message of the error.
22771
- */
22772
- function getErrorMessage(error) {
22773
- return toErrorWithMessage(error).message;
22774
- }
22775
23216
 
22776
23217
  /**
22777
- * @file Schema for modifier data.
22778
- */
22779
- /**
22780
- * Known validators that don't need to be validated as regex.
23218
+ * @file Utility functions for working with network rule nodes
22781
23219
  */
22782
- const KNOWN_VALIDATORS = new Set(['domain', 'pipe_separated_domains', 'regexp', 'url']);
22783
23220
  /**
22784
- * Zod schema for modifier data.
23221
+ * Creates a network rule node
23222
+ *
23223
+ * @param pattern Rule pattern
23224
+ * @param modifiers Rule modifiers (optional, default: undefined)
23225
+ * @param exception Exception rule flag (optional, default: false)
23226
+ * @param syntax Adblock syntax (optional, default: Common)
23227
+ * @returns Network rule node
22785
23228
  */
22786
- zodToCamelCase(baseCompatibilityDataSchema.extend({
22787
- /**
22788
- * List of modifiers that are incompatible with the actual one.
22789
- */
22790
- conflicts: zod.array(nonEmptyStringSchema).nullable().default(null),
22791
- /**
22792
- * The actual modifier is incompatible with all other modifiers, except the ones listed in `conflicts`.
22793
- */
22794
- inverse_conflicts: booleanSchema.default(false),
22795
- /**
22796
- * Describes whether the actual modifier supports value assignment. For example, `$domain` is assignable,
22797
- * so it can be used like this: `$domain=domain.com\|~subdomain.domain.com`, where `=` is the assignment operator
22798
- * and `domain.com\|~subdomain.domain.com` is the value.
22799
- */
22800
- assignable: booleanSchema.default(false),
22801
- /**
22802
- * Describes whether the actual modifier can be negated. For example, `$third-party` is negatable,
22803
- * so it can be used like this: `$~third-party`.
22804
- */
22805
- negatable: booleanSchema.default(true),
22806
- /**
22807
- * The actual modifier can only be used in blocking rules, it cannot be used in exceptions.
22808
- * If it's value is `true`, then the modifier can be used only in blocking rules.
22809
- * `exception_only` and `block_only` cannot be used together (they are mutually exclusive).
22810
- */
22811
- block_only: booleanSchema.default(false),
22812
- /**
22813
- * The actual modifier can only be used in exceptions, it cannot be used in blocking rules.
22814
- * If it's value is `true`, then the modifier can be used only in exceptions.
22815
- * `exception_only` and `block_only` cannot be used together (they are mutually exclusive).
22816
- */
22817
- exception_only: booleanSchema.default(false),
22818
- /**
22819
- * Describes whether the *assignable* modifier value is required.
22820
- * For example, `$cookie` is assignable but it can be used without a value in exception rules:
22821
- * `@@\|\|example.com^$cookie`.
22822
- * If `false`, the `value_format` is required, e.g. the value of `$app` should always be specified
22823
- */
22824
- value_optional: booleanSchema.default(false),
22825
- /**
22826
- * Describes the format of the value for the *assignable* modifier.
22827
- * Its value can be a regex pattern or a known validator name (e.g. `domain`, `pipe_separated_domains`, etc.).
22828
- */
22829
- value_format: nonEmptyStringSchema.nullable().default(null)
22830
- }).superRefine((data, ctx) => {
22831
- // TODO: find something better, for now we can't add refine logic to the base schema:
22832
- // https://github.com/colinhacks/zod/issues/454#issuecomment-848370721
22833
- baseRefineLogic(data, ctx);
22834
- if (data.block_only && data.exception_only) {
22835
- ctx.addIssue({
22836
- code: zod.ZodIssueCode.custom,
22837
- message: 'block_only and exception_only are mutually exclusive'
22838
- });
22839
- }
22840
- if (data.assignable && !data.value_format) {
22841
- ctx.addIssue({
22842
- code: zod.ZodIssueCode.custom,
22843
- message: 'value_format is required for assignable modifiers'
22844
- });
22845
- }
22846
- if (data.value_format) {
22847
- const valueFormat = data.value_format.trim();
22848
- // if it is a known validator, we don't need to validate it further
22849
- if (KNOWN_VALIDATORS.has(valueFormat)) {
22850
- return;
22851
- }
22852
- // otherwise, we need to validate it as a regex
22853
- try {
22854
- XRegExp(valueFormat);
22855
- } catch (error) {
22856
- ctx.addIssue({
22857
- code: zod.ZodIssueCode.custom,
22858
- message: getErrorMessage(error)
22859
- });
23229
+ function createNetworkRuleNode(pattern, modifiers = undefined, exception = false, syntax = AdblockSyntax.Common) {
23230
+ const result = {
23231
+ category: RuleCategory.Network,
23232
+ type: NetworkRuleType.NetworkRule,
23233
+ syntax,
23234
+ exception,
23235
+ pattern: {
23236
+ type: 'Value',
23237
+ value: pattern
22860
23238
  }
23239
+ };
23240
+ if (!isUndefined(modifiers)) {
23241
+ result.modifiers = clone(modifiers);
22861
23242
  }
22862
- }));
23243
+ return result;
23244
+ }
22863
23245
 
22864
23246
  /**
22865
- * @file Schema for redirect data.
23247
+ * @file Converter for request header removal rules
22866
23248
  */
23249
+ const UBO_RESPONSEHEADER_FN = 'responseheader';
23250
+ const ADG_REMOVEHEADER_MODIFIER = 'removeheader';
23251
+ const ERROR_MESSAGES = {
23252
+ EMPTY_PARAMETER: `Empty parameter for '${UBO_RESPONSEHEADER_FN}' function`,
23253
+ EXPECTED_END_OF_RULE: "Expected end of rule, but got '%s'",
23254
+ MULTIPLE_DOMAINS_NOT_SUPPORTED: 'Multiple domains are not supported yet'
23255
+ };
22867
23256
  /**
22868
- * Zod schema for redirect data.
23257
+ * Converter for request header removal rules
23258
+ *
23259
+ * @todo Implement `convertToUbo` (ABP currently doesn't support header removal rules)
22869
23260
  */
22870
- zodToCamelCase(baseCompatibilityDataSchema.extend({
23261
+ class HeaderRemovalRuleConverter extends RuleConverterBase {
22871
23262
  /**
22872
- * Whether the redirect is blocking.
23263
+ * Converts a header removal rule to AdGuard syntax, if possible.
23264
+ *
23265
+ * @param rule Rule node to convert
23266
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
23267
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
23268
+ * If the rule was not converted, the result array will contain the original node with the same object reference
23269
+ * @throws If the rule is invalid or cannot be converted
23270
+ * @example
23271
+ * If the input rule is:
23272
+ * ```adblock
23273
+ * example.com##^responseheader(header-name)
23274
+ * ```
23275
+ * The output will be:
23276
+ * ```adblock
23277
+ * ||example.com^$removeheader=header-name
23278
+ * ```
22873
23279
  */
22874
- is_blocking: booleanSchema.default(false)
22875
- }).superRefine(baseRefineLogic));
23280
+ static convertToAdg(rule) {
23281
+ // TODO: Add support for ABP syntax once it starts supporting header removal rules
23282
+ // Leave the rule as is if it's not a header removal rule
23283
+ if (rule.category !== RuleCategory.Cosmetic || rule.type !== CosmeticRuleType.HtmlFilteringRule) {
23284
+ return createNodeConversionResult([rule], false);
23285
+ }
23286
+ const stream = new CssTokenStream(rule.body.value);
23287
+ let token;
23288
+ // Skip leading whitespace
23289
+ stream.skipWhitespace();
23290
+ // Next token should be the `^` followed by a `responseheader` function
23291
+ token = stream.get();
23292
+ if (!token || token.type !== TokenType$1.Delim || rule.body.value[token.start] !== UBO_HTML_MASK) {
23293
+ return createNodeConversionResult([rule], false);
23294
+ }
23295
+ stream.advance();
23296
+ token = stream.get();
23297
+ if (!token) {
23298
+ return createNodeConversionResult([rule], false);
23299
+ }
23300
+ const functionName = rule.body.value.slice(token.start, token.end - 1);
23301
+ if (functionName !== UBO_RESPONSEHEADER_FN) {
23302
+ return createNodeConversionResult([rule], false);
23303
+ }
23304
+ // Parse the parameter
23305
+ const paramStart = token.end;
23306
+ stream.skipUntilBalanced();
23307
+ const paramEnd = stream.getOrFail().end;
23308
+ const param = rule.body.value.slice(paramStart, paramEnd - 1).trim();
23309
+ // Do not allow empty parameter
23310
+ if (param.length === 0) {
23311
+ throw new RuleConversionError(ERROR_MESSAGES.EMPTY_PARAMETER);
23312
+ }
23313
+ stream.expect(TokenType$1.CloseParenthesis);
23314
+ stream.advance();
23315
+ // Skip trailing whitespace after the function call
23316
+ stream.skipWhitespace();
23317
+ // Expect the end of the rule - so nothing should be left in the stream
23318
+ if (!stream.isEof()) {
23319
+ token = stream.getOrFail();
23320
+ throw new RuleConversionError(sprintf(ERROR_MESSAGES.EXPECTED_END_OF_RULE, getFormattedTokenName(token.type)));
23321
+ }
23322
+ // Prepare network rule pattern
23323
+ const pattern = [];
23324
+ if (rule.domains.children.length === 1) {
23325
+ // If the rule has only one domain, we can use a simple network rule pattern:
23326
+ // ||single-domain-from-the-rule^
23327
+ pattern.push(ADBLOCK_URL_START, rule.domains.children[0].value, ADBLOCK_URL_SEPARATOR);
23328
+ } else if (rule.domains.children.length > 1) {
23329
+ // TODO: Add support for multiple domains, for example:
23330
+ // example.com,example.org,example.net##^responseheader(header-name)
23331
+ // We should consider allowing $domain with $removeheader modifier,
23332
+ // for example:
23333
+ // $removeheader=header-name,domain=example.com|example.org|example.net
23334
+ throw new RuleConversionError(ERROR_MESSAGES.MULTIPLE_DOMAINS_NOT_SUPPORTED);
23335
+ }
23336
+ // Prepare network rule modifiers
23337
+ const modifiers = createModifierListNode();
23338
+ modifiers.children.push(createModifierNode(ADG_REMOVEHEADER_MODIFIER, param));
23339
+ // Construct the network rule
23340
+ return createNodeConversionResult([createNetworkRuleNode(pattern.join(EMPTY), modifiers,
23341
+ // Copy the exception flag
23342
+ rule.exception, AdblockSyntax.Adg)], true);
23343
+ }
23344
+ }
22876
23345
 
22877
23346
  /**
22878
- * @file Schema for scriptlet data.
22879
- */
22880
- /**
22881
- * Zod schema for scriptlet parameter data.
22882
- */
22883
- const scriptletParameterSchema = zod.object({
22884
- /**
22885
- * Name of the actual parameter.
22886
- */
22887
- name: nonEmptyStringSchema,
22888
- /**
22889
- * Describes whether the parameter is required. Empty parameters are not allowed.
22890
- */
22891
- required: booleanSchema,
22892
- /**
22893
- * Short description of the parameter.
22894
- * If not specified or it's value is `null`,then the description is not available.
22895
- */
22896
- description: nonEmptyStringSchema.nullable().default(null),
22897
- /**
22898
- * Regular expression that matches the value of the parameter.
22899
- * If it's value is `null`, then the parameter value is not checked.
22900
- */
22901
- pattern: nonEmptyStringSchema.nullable().default(null),
22902
- /**
22903
- * Default value of the parameter (if any).
22904
- */
22905
- default: nonEmptyStringSchema.nullable().default(null),
22906
- /**
22907
- * Describes whether the parameter is used only for debugging purposes.
22908
- */
22909
- debug: booleanSchema.default(false)
22910
- });
22911
- /**
22912
- * Zod schema for scriptlet parameters.
23347
+ * @file Cosmetic rule converter
22913
23348
  */
22914
- const scriptletParametersSchema = zod.array(scriptletParameterSchema);
22915
23349
  /**
22916
- * Zod schema for scriptlet data.
23350
+ * Cosmetic rule converter class (also known as "non-basic rule converter")
23351
+ *
23352
+ * @todo Implement `convertToUbo` and `convertToAbp`
22917
23353
  */
22918
- zodToCamelCase(baseCompatibilityDataSchema.extend({
23354
+ class CosmeticRuleConverter extends RuleConverterBase {
22919
23355
  /**
22920
- * List of parameters that the scriptlet accepts.
22921
- * **Every** parameter should be listed here, because we check that the scriptlet is used correctly
22922
- * (e.g. that the number of parameters is correct).
23356
+ * Converts a cosmetic rule to AdGuard syntax, if possible.
23357
+ *
23358
+ * @param rule Rule node to convert
23359
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
23360
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
23361
+ * If the rule was not converted, the result array will contain the original node with the same object reference
23362
+ * @throws If the rule is invalid or cannot be converted
22923
23363
  */
22924
- parameters: scriptletParametersSchema.optional()
22925
- }).superRefine((data, ctx) => {
22926
- // TODO: find something better, for now we can't add refine logic to the base schema:
22927
- // https://github.com/colinhacks/zod/issues/454#issuecomment-848370721
22928
- baseRefineLogic(data, ctx);
22929
- // we don't allow required parameters after optional ones
22930
- if (!data.parameters) {
22931
- return;
22932
- }
22933
- let optionalFound = false;
22934
- for (const parameter of data.parameters) {
22935
- if (optionalFound && parameter.required) {
22936
- ctx.addIssue({
22937
- code: zod.ZodIssueCode.custom,
22938
- message: 'Required parameters must be before optional ones'
23364
+ static convertToAdg(rule) {
23365
+ let subconverterResult;
23366
+ // Convert cosmetic rule based on its type
23367
+ switch (rule.type) {
23368
+ case CosmeticRuleType.ElementHidingRule:
23369
+ subconverterResult = ElementHidingRuleConverter.convertToAdg(rule);
23370
+ break;
23371
+ case CosmeticRuleType.ScriptletInjectionRule:
23372
+ subconverterResult = ScriptletRuleConverter.convertToAdg(rule);
23373
+ break;
23374
+ case CosmeticRuleType.CssInjectionRule:
23375
+ subconverterResult = CssInjectionRuleConverter.convertToAdg(rule);
23376
+ break;
23377
+ case CosmeticRuleType.HtmlFilteringRule:
23378
+ // Handle special case: uBO response header filtering rule
23379
+ // TODO: Optimize double CSS tokenization here
23380
+ subconverterResult = HeaderRemovalRuleConverter.convertToAdg(rule);
23381
+ if (subconverterResult.isConverted) {
23382
+ break;
23383
+ }
23384
+ subconverterResult = HtmlRuleConverter.convertToAdg(rule);
23385
+ break;
23386
+ // Note: Currently, only ADG supports JS injection rules, so we don't need to convert them
23387
+ case CosmeticRuleType.JsInjectionRule:
23388
+ subconverterResult = createNodeConversionResult([rule], false);
23389
+ break;
23390
+ default:
23391
+ throw new RuleConversionError('Unsupported cosmetic rule type');
23392
+ }
23393
+ let convertedModifiers;
23394
+ // Convert cosmetic rule modifiers, if any
23395
+ if (rule.modifiers) {
23396
+ if (rule.syntax === AdblockSyntax.Ubo) {
23397
+ // uBO doesn't support this rule:
23398
+ // example.com##+js(set-constant.js, foo, bar):matches-path(/baz)
23399
+ if (rule.type === CosmeticRuleType.ScriptletInjectionRule) {
23400
+ throw new RuleConversionError('uBO scriptlet injection rules don\'t support cosmetic rule modifiers');
23401
+ }
23402
+ convertedModifiers = AdgCosmeticRuleModifierConverter.convertFromUbo(rule.modifiers);
23403
+ } else if (rule.syntax === AdblockSyntax.Abp) {
23404
+ // TODO: Implement once ABP starts supporting cosmetic rule modifiers
23405
+ throw new RuleConversionError('ABP don\'t support cosmetic rule modifiers');
23406
+ }
23407
+ }
23408
+ if (subconverterResult.result.length > 1 || subconverterResult.isConverted || convertedModifiers && convertedModifiers.isConverted) {
23409
+ // Add modifier list to the subconverter result rules
23410
+ subconverterResult.result.forEach(subconverterRule => {
23411
+ if (convertedModifiers && subconverterRule.category === RuleCategory.Cosmetic) {
23412
+ // eslint-disable-next-line no-param-reassign
23413
+ subconverterRule.modifiers = convertedModifiers.result;
23414
+ }
22939
23415
  });
23416
+ return subconverterResult;
22940
23417
  }
22941
- if (!parameter.required) {
22942
- optionalFound = true;
23418
+ return createNodeConversionResult([rule], false);
23419
+ }
23420
+ /**
23421
+ * Converts a cosmetic rule to uBlock Origin syntax, if possible.
23422
+ *
23423
+ * @param rule Rule node to convert
23424
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
23425
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
23426
+ * If the rule was not converted, the result array will contain the original node with the same object reference
23427
+ * @throws If the rule is invalid or cannot be converted
23428
+ */
23429
+ // TODO: Add support for other cosmetic rule types
23430
+ static convertToUbo(rule) {
23431
+ // Convert cosmetic rule based on its type
23432
+ if (rule.type === CosmeticRuleType.ScriptletInjectionRule) {
23433
+ if (rule.syntax === AdblockSyntax.Adg && rule.modifiers?.children.length) {
23434
+ // e.g. example.com##+js(set-constant.js, foo, bar):matches-path(/baz)
23435
+ throw new RuleConversionError('uBO scriptlet injection rules do not support cosmetic rule modifiers');
23436
+ }
23437
+ return ScriptletRuleConverter.convertToUbo(rule);
22943
23438
  }
23439
+ return createNodeConversionResult([rule], false);
22944
23440
  }
22945
- }));
23441
+ }
22946
23442
 
22947
23443
  /**
22948
23444
  * @file Network rule modifier list converter.
@@ -22969,6 +23465,10 @@ const REDIRECT_MODIFIER = 'redirect';
22969
23465
  * @see {@link https://adguard.com/kb/general/ad-filtering/create-own-filters/#redirect-rule-modifier}
22970
23466
  */
22971
23467
  const REDIRECT_RULE_MODIFIER = 'redirect-rule';
23468
+ /**
23469
+ * @see {@link https://github.com/gorhill/uBlock/wiki/Resources-Library#empty-redirect-resources}
23470
+ */
23471
+ const UBO_NOOP_TEXT_RESOURCE = 'noop.txt';
22972
23472
  /**
22973
23473
  * Redirect-related modifiers.
22974
23474
  */
@@ -23118,6 +23618,112 @@ class NetworkRuleModifierListConverter extends ConverterBase {
23118
23618
  }
23119
23619
  return createConversionResult(modifierList, false);
23120
23620
  }
23621
+ /**
23622
+ * Converts a network rule modifier list to uBlock format, if possible.
23623
+ *
23624
+ * @param modifierList Network rule modifier list node to convert
23625
+ * @param isException If `true`, the rule is an exception rule
23626
+ * @returns An object which follows the {@link ConversionResult} interface. Its `result` property contains
23627
+ * the converted node, and its `isConverted` flag indicates whether the original node was converted.
23628
+ * If the node was not converted, the result will contain the original node with the same object reference
23629
+ * @throws If the conversion is not possible
23630
+ */
23631
+ // TODO: Optimize
23632
+ static convertToUbo(modifierList, isException = false) {
23633
+ const conversionMap = new MultiValueMap();
23634
+ const resourceTypeModifiersToAdd = new Set();
23635
+ modifierList.children.forEach((modifierNode, index) => {
23636
+ const originalModifierName = modifierNode.name.value;
23637
+ const modifierData = modifiersCompatibilityTable.getFirst(originalModifierName, GenericPlatform.UboAny);
23638
+ // Handle special case: resource redirection modifiers
23639
+ if (REDIRECT_MODIFIERS.has(originalModifierName)) {
23640
+ // Redirect modifiers cannot be negated
23641
+ if (modifierNode.exception === true) {
23642
+ throw new RuleConversionError(`Modifier '${modifierNode.name.value}' cannot be negated`);
23643
+ }
23644
+ // Convert the redirect resource name to uBO format
23645
+ const redirectResourceName = modifierNode.value?.value;
23646
+ // Special case: for exception rules, $redirect without value is allowed,
23647
+ // and in this case it means an exception for all redirects
23648
+ if (!redirectResourceName && !isException) {
23649
+ throw new RuleConversionError(`No redirect resource specified for '${modifierNode.name.value}' modifier`);
23650
+ }
23651
+ if (!redirectResourceName) {
23652
+ // Jump to the next modifier if the redirect resource is not specified
23653
+ return;
23654
+ }
23655
+ // Leave $redirect and $redirect-rule modifiers as is, but convert $rewrite to $redirect
23656
+ const modifierName = modifierNode.name.value === ABP_REWRITE_MODIFIER ? REDIRECT_MODIFIER : modifierNode.name.value;
23657
+ const convertedRedirectResourceData = redirectsCompatibilityTable.getFirst(redirectResourceName, GenericPlatform.UboAny);
23658
+ const convertedRedirectResourceName = convertedRedirectResourceData?.name ?? redirectResourceName;
23659
+ // uBlock requires the $redirect modifier to have a resource type
23660
+ // https://github.com/AdguardTeam/Scriptlets/issues/101
23661
+ if (convertedRedirectResourceData?.resourceTypes?.length) {
23662
+ // Convert the resource types to uBO modifiers
23663
+ const uboResourceTypeModifiers = redirectsCompatibilityTable.getResourceTypeModifiers(convertedRedirectResourceData, GenericPlatform.UboAny);
23664
+ // Special case: noop text resource
23665
+ // If any of resource type is already present, we don't need to add other resource types,
23666
+ // otherwise, add all resource types
23667
+ // TODO: Optimize this logic
23668
+ // Check if the current resource is the noop text resource
23669
+ const isNoopTextResource = convertedRedirectResourceName === UBO_NOOP_TEXT_RESOURCE;
23670
+ // Determine if there are any valid resource types already present
23671
+ const hasValidResourceType = modifierList.children.some(modifier => {
23672
+ const name = modifier.name.value;
23673
+ if (!isValidResourceType(name)) {
23674
+ return false;
23675
+ }
23676
+ const convertedModifierData = modifiersCompatibilityTable.getFirst(name, GenericPlatform.UboAny);
23677
+ return uboResourceTypeModifiers.has(convertedModifierData?.name ?? name);
23678
+ });
23679
+ // If it's not the noop text resource or if no valid resource types are present
23680
+ if (!isNoopTextResource || !hasValidResourceType) {
23681
+ uboResourceTypeModifiers.forEach(resourceType => {
23682
+ resourceTypeModifiersToAdd.add(resourceType);
23683
+ });
23684
+ }
23685
+ }
23686
+ // Check if the modifier name or the redirect resource name is different from the original modifier.
23687
+ // If so, add the converted modifier to the list
23688
+ if (modifierName !== originalModifierName || !isUndefined(convertedRedirectResourceName) && convertedRedirectResourceName !== redirectResourceName) {
23689
+ conversionMap.add(index, createModifierNode(modifierName,
23690
+ // If the redirect resource name is unknown, fall back to the original one
23691
+ // Later, the validator will throw an error if the resource name is invalid
23692
+ convertedRedirectResourceName || redirectResourceName, modifierNode.exception));
23693
+ }
23694
+ return;
23695
+ }
23696
+ // Generic modifier conversion
23697
+ if (modifierData && modifierData.name !== originalModifierName) {
23698
+ conversionMap.add(index, createModifierNode(modifierData.name, modifierNode.value?.value, modifierNode.exception));
23699
+ }
23700
+ });
23701
+ // Prepare the result if there are any converted modifiers or $csp modifiers
23702
+ if (conversionMap.size || resourceTypeModifiersToAdd.size) {
23703
+ const modifierListClone = cloneModifierListNode(modifierList);
23704
+ // Replace the original modifiers with the converted ones
23705
+ // One modifier may be replaced with multiple modifiers, so we need to flatten the array
23706
+ modifierListClone.children = modifierListClone.children.map((modifierNode, index) => {
23707
+ const conversionRecord = conversionMap.get(index);
23708
+ if (conversionRecord) {
23709
+ return conversionRecord;
23710
+ }
23711
+ return modifierNode;
23712
+ }).flat();
23713
+ // Before returning the result, remove duplicated modifiers
23714
+ modifierListClone.children = modifierListClone.children.filter((modifierNode, index, self) => self.findIndex(m => m.name.value === modifierNode.name.value && m.exception === modifierNode.exception && m.value?.value === modifierNode.value?.value) === index);
23715
+ if (resourceTypeModifiersToAdd.size) {
23716
+ const modifierNameSet = new Set(modifierList.children.map(m => m.name.value));
23717
+ resourceTypeModifiersToAdd.forEach(resourceType => {
23718
+ if (!modifierNameSet.has(resourceType)) {
23719
+ modifierListClone.children.push(createModifierNode(resourceType));
23720
+ }
23721
+ });
23722
+ }
23723
+ return createConversionResult(modifierListClone, true);
23724
+ }
23725
+ return createConversionResult(modifierList, false);
23726
+ }
23121
23727
  }
23122
23728
 
23123
23729
  /**
@@ -23167,6 +23773,44 @@ class NetworkRuleConverter extends RuleConverterBase {
23167
23773
  // If the modifiers were not converted, return the original rule
23168
23774
  return createNodeConversionResult([rule], false);
23169
23775
  }
23776
+ /**
23777
+ * Converts a network rule to uBlock format, if possible.
23778
+ *
23779
+ * @param rule Rule node to convert
23780
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
23781
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
23782
+ * If the rule was not converted, the result array will contain the original node with the same object reference
23783
+ * @throws If the rule is invalid or cannot be converted
23784
+ */
23785
+ static convertToUbo(rule) {
23786
+ // TODO: add support for host rules
23787
+ if (rule.type !== NetworkRuleType.NetworkRule) {
23788
+ throw new Error(`Invalid rule type: ${rule.type}`);
23789
+ }
23790
+ if (rule.modifiers) {
23791
+ const modifiers = NetworkRuleModifierListConverter.convertToUbo(rule.modifiers, rule.exception);
23792
+ // If the object reference is different, it means that the modifiers were converted
23793
+ // In this case, we should clone the entire rule and replace the modifiers with the converted ones
23794
+ if (modifiers.isConverted) {
23795
+ return {
23796
+ result: [{
23797
+ category: RuleCategory.Network,
23798
+ type: NetworkRuleType.NetworkRule,
23799
+ syntax: rule.syntax,
23800
+ exception: rule.exception,
23801
+ pattern: {
23802
+ type: 'Value',
23803
+ value: rule.pattern.value
23804
+ },
23805
+ modifiers: modifiers.result
23806
+ }],
23807
+ isConverted: true
23808
+ };
23809
+ }
23810
+ }
23811
+ // If the modifiers were not converted, return the original rule
23812
+ return createNodeConversionResult([rule], false);
23813
+ }
23170
23814
  }
23171
23815
 
23172
23816
  /**
@@ -23214,6 +23858,25 @@ class RuleConverter extends RuleConverterBase {
23214
23858
  throw new RuleConversionError('Unknown rule category');
23215
23859
  }
23216
23860
  }
23861
+ /**
23862
+ * Converts an adblock filtering rule to uBlock Origin format, if possible.
23863
+ *
23864
+ * @param rule Rule node to convert
23865
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
23866
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
23867
+ * If the rule was not converted, the result array will contain the original node with the same object reference
23868
+ * @throws If the rule is invalid or cannot be converted
23869
+ */
23870
+ // TODO: Add support for other rule types
23871
+ static convertToUbo(rule) {
23872
+ if (rule.category === RuleCategory.Cosmetic) {
23873
+ return CosmeticRuleConverter.convertToUbo(rule);
23874
+ }
23875
+ if (rule.category === RuleCategory.Network) {
23876
+ return NetworkRuleConverter.convertToUbo(rule);
23877
+ }
23878
+ return createConversionResult([rule], false);
23879
+ }
23217
23880
  }
23218
23881
 
23219
23882
  /**
@@ -23607,7 +24270,7 @@ class ByteBuffer {
23607
24270
  * @see {@link https://stackoverflow.com/a/62797156}
23608
24271
  */
23609
24272
  const isChromium = () => {
23610
- return typeof window !== 'undefined' && (Object.prototype.hasOwnProperty.call(window, 'chrome') || typeof window.navigator !== 'undefined' && /chrome/i.test(window.navigator.userAgent || ''));
24273
+ return typeof window !== 'undefined' && (Object.prototype.hasOwnProperty.call(window, 'chrome') || typeof window.navigator !== 'undefined' && /chrome/i.test(window.navigator.userAgent));
23611
24274
  };
23612
24275
 
23613
24276
  /* eslint-disable no-param-reassign */
@@ -23698,16 +24361,23 @@ class OutputByteBuffer extends ByteBuffer {
23698
24361
  */
23699
24362
  offset;
23700
24363
  /**
23701
- * Size of the shared buffer for encoding strings.
24364
+ * Size of the shared buffer for encoding strings in bytes.
24365
+ * This is a divisor of ByteBuffer.CHUNK_SIZE and experience shows that this value works optimally.
24366
+ * This is sufficient for most strings that occur in filter lists (we checked average string length in popular
24367
+ * filter lists).
23702
24368
  */
23703
24369
  static ENCODER_BUFFER_SIZE = 8192;
23704
24370
  /**
23705
- * Threshold for using a shared buffer for encoding strings.
24371
+ * Length threshold for using a shared buffer for encoding strings.
24372
+ * This temp buffer is needed because we write the short strings in it
24373
+ * (so there is no need to constantly allocate a new buffer).
24374
+ * The reason for dividing ENCODER_BUFFER_SIZE by 4 is to ensure that the encoded string fits in the buffer,
24375
+ * if we also take into account the worst possible case (each character is encoded with 4 bytes).
23706
24376
  */
23707
24377
  static SHORT_STRING_THRESHOLD = 2048; // 8192 / 4
23708
24378
  /**
23709
24379
  * Represents the maximum value that can be written as a 'storage optimized' unsigned integer.
23710
- * 0x1FFFFFFF means 32 bits minus 3 bits, because the last bit in each byte is a flag indicating
24380
+ * 0x1FFFFFFF means 29 bits — 32 bits minus 3 bits because the last bit in each byte is a flag indicating
23711
24381
  * if there are more bytes (except for the last byte).
23712
24382
  */
23713
24383
  static MAX_OPTIMIZED_UINT = 0x1FFFFFFF;
@@ -24254,7 +24924,7 @@ class RuleCategorizer {
24254
24924
  }
24255
24925
  }
24256
24926
  }
24257
- const version = "2.0.0-alpha.0";
24927
+ const version = "2.0.1";
24258
24928
 
24259
24929
  /**
24260
24930
  * @file AGTree version
@@ -24264,4 +24934,4 @@ const version = "2.0.0-alpha.0";
24264
24934
  // `tsc` in the root directory, it will generate `dist/types/src/version.d.ts`
24265
24935
  // with wrong relative path to `package.json`. So we need this little "hack"
24266
24936
  const AGTREE_VERSION = version;
24267
- export { ADBLOCK_URL_SEPARATOR, ADBLOCK_URL_SEPARATOR_REGEX, ADBLOCK_URL_START, ADBLOCK_URL_START_REGEX, ADBLOCK_WILDCARD, ADBLOCK_WILDCARD_REGEX, ADG_SCRIPTLET_MASK, AGLINT_COMMAND_PREFIX, AGTREE_VERSION, AdblockSyntax, AdblockSyntaxError, AgentCommentRuleParser, AgentParser, AppListParser, BINARY_SCHEMA_VERSION, BinarySchemaMismatchError, ByteBuffer, COMMA_DOMAIN_LIST_SEPARATOR, CommentMarker, CommentRuleParser, CommentRuleType, ConfigCommentRuleParser, CosmeticRuleParser, CosmeticRuleSeparator, CosmeticRuleSeparatorUtils, CosmeticRuleType, DomainListParser, DomainUtils, EXT_CSS_LEGACY_ATTRIBUTES, EXT_CSS_PSEUDO_CLASSES, FORBIDDEN_CSS_FUNCTIONS, FilterListConverter, FilterListParser, GenericPlatform, HINT_MARKER, HintCommentRuleParser, HintParser, HostRuleParser, IF, INCLUDE, InputByteBuffer, KNOWN_METADATA_HEADERS, LogicalExpressionParser, LogicalExpressionUtils, MODIFIERS_SEPARATOR, MODIFIER_ASSIGN_OPERATOR, MetadataCommentRuleParser, MethodListParser, ModifierListParser, ModifierParser, NEGATION_MARKER, NETWORK_RULE_EXCEPTION_MARKER, NETWORK_RULE_SEPARATOR, NetworkRuleParser, NetworkRuleType, NotImplementedError, OutputByteBuffer, PIPE_MODIFIER_SEPARATOR, PREPROCESSOR_MARKER, ParameterListParser, PositionProvider, PreProcessorCommentRuleParser, QuoteType, QuoteUtils, RawFilterListConverter, RawRuleConverter, RegExpUtils, RuleCategorizer, RuleCategory, RuleConversionError, RuleConverter, RuleParser, SAFARI_CB_AFFINITY, SPECIAL_REGEX_SYMBOLS, SpecificPlatform, StealthOptionListParser, UBO_SCRIPTLET_MASK, decodeTextPolyfill, defaultParserOptions, encodeIntoPolyfill, getPlatformId, getSpecificPlatformName, isGenericPlatform, modifierValidator, modifiersCompatibilityTable, parseRawPlatforms, redirectsCompatibilityTable, scriptletsCompatibilityTable };
24937
+ export { ADBLOCK_URL_SEPARATOR, ADBLOCK_URL_SEPARATOR_REGEX, ADBLOCK_URL_START, ADBLOCK_URL_START_REGEX, ADBLOCK_WILDCARD, ADBLOCK_WILDCARD_REGEX, ADG_SCRIPTLET_MASK, AGLINT_COMMAND_PREFIX, AGTREE_VERSION, AdblockSyntax, AdblockSyntaxError, AgentCommentRuleParser, AgentParser, AppListParser, BINARY_SCHEMA_VERSION, BinarySchemaMismatchError, ByteBuffer, COMMA_DOMAIN_LIST_SEPARATOR, CommentMarker, CommentRuleParser, CommentRuleType, ConfigCommentRuleParser, CosmeticRuleParser, CosmeticRuleSeparator, CosmeticRuleSeparatorUtils, CosmeticRuleType, DomainListParser, DomainUtils, EXT_CSS_LEGACY_ATTRIBUTES, EXT_CSS_PSEUDO_CLASSES, FORBIDDEN_CSS_FUNCTIONS, FilterListConverter, FilterListParser, GenericPlatform, HINT_MARKER, HintCommentRuleParser, HintParser, HostRuleParser, IF, INCLUDE, InputByteBuffer, KNOWN_METADATA_HEADERS, LogicalExpressionParser, LogicalExpressionUtils, MODIFIERS_SEPARATOR, MODIFIER_ASSIGN_OPERATOR, MetadataCommentRuleParser, MethodListParser, ModifierListParser, ModifierParser, NEGATION_MARKER, NETWORK_RULE_EXCEPTION_MARKER, NETWORK_RULE_SEPARATOR, NetworkRuleParser, NetworkRuleType, NotImplementedError, OutputByteBuffer, PIPE_MODIFIER_SEPARATOR, PREPROCESSOR_MARKER, ParameterListParser, PositionProvider, PreProcessorCommentRuleParser, QuoteType, QuoteUtils, RawFilterListConverter, RawRuleConverter, RegExpUtils, ResourceType, RuleCategorizer, RuleCategory, RuleConversionError, RuleConverter, RuleParser, SAFARI_CB_AFFINITY, SPECIAL_REGEX_SYMBOLS, SpecificPlatform, StealthOptionListParser, UBO_SCRIPTLET_MASK, decodeTextPolyfill, defaultParserOptions, encodeIntoPolyfill, getPlatformId, getResourceTypeModifier, getSpecificPlatformName, isGenericPlatform, isValidResourceType, modifierValidator, modifiersCompatibilityTable, parseRawPlatforms, redirectsCompatibilityTable, scriptletsCompatibilityTable };