AdBlock Script for WebView

Parse ABP Cosmetic rules to CSS and apply it.

As of 2023-05-22. See the latest version.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name               AdBlock Script for WebView
// @name:zh-CN         套壳油猴的广告拦截脚本
// @author             Lemon399
// @version            2.5.4.1
// @description        Parse ABP Cosmetic rules to CSS and apply it.
// @description:zh-CN  将 ABP 中的元素隐藏规则转换为 CSS 使用
// @resource           jiekouAD https://raw.fastgit.ixmu.net/damengzhu/banad/main/jiekouAD.txt
// @resource           CSSRule https://raw.fastgit.ixmu.net/damengzhu/abpmerge/main/CSSRule.txt
// @match              https://*/*
// @match              http://*/*
// @run-at             document-start
// @grant              unsafeWindow
// @grant              GM_registerMenuCommand
// @grant              GM_unregisterMenuCommand
// @grant              GM_getValue
// @grant              GM_deleteValue
// @grant              GM_setValue
// @grant              GM_addStyle
// @grant              GM_xmlhttpRequest
// @grant              GM_getResourceText
// @namespace          https://lemon399-bitbucket-io.vercel.app/
// @source             https://gitee.com/lemon399/tampermonkey-cli/tree/master/projects/abp_parse
// @source             https://bitbucket.org/lemon399/tampermonkey-cli/src/master/projects/abp_parse/
// @connect            raw.fastgit.ixmu.net
// @copyright          GPL-3.0
// @license            GPL-3.0
// ==/UserScript==

/* ==UserConfig==
配置:
  css:
    title: 隐藏 CSS 规则
    description: 隐藏广告使用的 CSS 规则
    type: text
    default: " {display: none !important;width: 0 !important;height: 0 !important;} "
  timeout:
    title: 规则下载超时
    description: 更新规则时,规则下载超时时间
    type: number
    default: 10000
    min: 0
    unit: 毫秒
  tryCount:
    title: CSS 注入尝试次数
    description: 某些框架会重建页面,需要多次注入,只有检测到 CSS 不存在时才会尝试再次注入
    type: number
    default: 5
    min: 0
    unit: 次
  tryTimeout:
    title: CSS 注入尝试间隔
    description: 两次注入尝试的间隔时间
    type: number
    default: 500
    min: 100
    unit: 毫秒
==/UserConfig== */

/* eslint-disable no-undef */

(function (cat) {
  "use strict";

  function __awaiter(thisArg, _arguments, P, generator) {
    function adopt(value) {
      return value instanceof P
        ? value
        : new P(function (resolve) {
            resolve(value);
          });
    }
    return new (P || (P = Promise))(function (resolve, reject) {
      function fulfilled(value) {
        try {
          step(generator.next(value));
        } catch (e) {
          reject(e);
        }
      }
      function rejected(value) {
        try {
          step(generator["throw"](value));
        } catch (e) {
          reject(e);
        }
      }
      function step(result) {
        result.done
          ? resolve(result.value)
          : adopt(result.value).then(fulfilled, rejected);
      }
      step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
  }

  const userConfig = {
      css: " {display: none !important;width: 0 !important;height: 0 !important;} ",
      timeout: 10000,
      tryCount: 5,
      tryTimeout: 500,
    },
    defaultRules = `
! 不支持的规则和开头为 ! 的行会忽略
!
! 由于语法限制,此处规则中
! 一个反斜杠需要改成两个,像这样 \\

`,
    onlineRules = [
      ...[
        {
          标识: "jiekouAD",
          地址: "https://raw.fastgit.ixmu.net/damengzhu/banad/main/jiekouAD.txt",
          在线更新: !!1,
          筛选后存储: !!1,
        },
        {
          标识: "CSSRule",
          地址: "https://raw.fastgit.ixmu.net/damengzhu/abpmerge/main/CSSRule.txt",
          在线更新: !!1,
          筛选后存储: !!0,
        },
      ],
      ...[],
    ];

  const CRRE =
      /^(\[\$domain=)?(~?[\w-]+(?:\.[\w-]+)*(?:\.[\w-]+|\.\*)(?:(?:,|\|)~?[\w-]+(?:\.[\w-]+)*(?:\.[\w-]+|\.\*))*)?(?:])?(#@?\$?\??#)([^\s^+].*)/,
    BRRE =
      /^(?:@@?)(?:\/(.*[^\\])\/|(\|\|?)?(https?:\/\/)?([^\s"<>`]+?[|^]?))?\$((?:(?:~?[\w-]+(?:=[^$]+)?|_+)(?:[^\\],|$))+)/,
    CCRE =
      /^\/\*\s(\d)(\|)?(.+?)\s\*\/\s((.+?)\s*{\s*[a-zA-Z-]+\s*:\s*.+})\s*$/,
    BROpts = [
      "elemhide",
      "ehide",
      "specifichide",
      "shide",
      "generichide",
      "ghide",
    ],
    CRFlags = ["##", "#@#", "#?#", "#@?#", "#$#", "#@$#", "#$?#", "#@$?#"],
    styleBoxes = ["genHideCss", "genExtraCss", "spcHideCss", "spcExtraCss"],
    dataBoxes = ["selectors", "extSelectors", "styles", "extStyles"];
  function bRuleSpliter(rule) {
    const group = rule.match(BRRE);
    if (!group) return null;
    const [, regex, pipe, proto, body, option] = group,
      options = option.split(","),
      sepChar = "[^\\w\\.%-]",
      anyChar = '(?:[^\\s"<>`]*)',
      eh = hasSome(options, ["elemhide", "ehide"]),
      sh = hasSome(options, ["specifichide", "shide"]),
      gh = hasSome(options, ["generichide", "ghide"]);
    let domains = [];
    options.forEach((opt) => {
      if (opt.startsWith("domain=")) {
        domains = opt.split("=")[1].split("|");
      }
    });
    let match = "";
    if (regex) {
      match = regex;
    } else if (body) {
      match += pipe
        ? proto
          ? `^${proto}`
          : `^https?://(?:[\\w-]+\\.)*?`
        : `^${anyChar}`;
      match += body
        .replace(/[-\\$+.()[\]{}]/g, "\\$&")
        .replace(/\^/g, "$^")
        .replace(/\|$/, "$")
        .replace(/\|/g, "\\|")
        .replace(/\*$/g, "")
        .replace(/\*/g, anyChar)
        .replace(/\$\^$/, `(?:${sepChar}.*|$)`)
        .replace(/\$\^/g, sepChar);
    } else if (domains.length > 0) {
      match = domains;
    }
    return {
      rule,
      match,
      level: eh || (gh && sh) ? 3 : sh ? 2 : gh ? 1 : 0,
    };
  }
  function isBasicRule(rule) {
    return BRRE.test(rule) && hasSome(rule, BROpts);
  }
  function bRuleParser(rule, url = location.href) {
    return rule
      ? (Array.isArray(rule.match) && domainChecker(rule.match)[0]) ||
        (!Array.isArray(rule.match) && new RegExp(rule.match).test(url))
        ? rule.level
        : 0
      : 0;
  }
  function getEtag(header) {
    var _a;
    let result = null;
    if (!header) return null;
    [
      /[e|E][t|T]ag:\s(?:W\/)?"(\w+)"/,
      // WebMonkey 系
      /[e|E][t|T]ag:\s\[(?:W\/)?"(\w+)"\]/,
      // 书签地球
      /[e|E][t|T]ag=(?:W\/)?"(\w+)"/,
      // 海阔世界
      /^(?:W\/)?"(\w+)"/,
    ].forEach((re) => {
      result !== null && result !== void 0
        ? result
        : (result = header.match(re));
    });
    return (_a = result === null || result === void 0 ? void 0 : result[1]) !==
      null && _a !== void 0
      ? _a
      : null;
  }
  function extrEtag(resp) {
    var _a, _b, _c;
    const etag = getEtag(
      typeof (resp === null || resp === void 0 ? void 0 : resp.headers) ==
        "object"
        ? // 海阔世界
          (_b =
            (_a = resp === null || resp === void 0 ? void 0 : resp.headers) ===
              null || _a === void 0
              ? void 0
              : _a.etag) === null || _b === void 0
          ? void 0
          : _b[0]
        : typeof (resp === null || resp === void 0
            ? void 0
            : resp.responseHeaders) == "string"
        ? // Tampermonkey
          resp === null || resp === void 0
          ? void 0
          : resp.responseHeaders
        : // Appara
        (_c =
            resp === null || resp === void 0
              ? void 0
              : resp.getAllResponseHeaders) === null || _c === void 0
        ? void 0
        : _c.call(resp)
    );
    return etag;
  }
  function makeRuleBox() {
    return {
      black: [],
      white: [],
    };
  }
  function domainChecker(domains) {
    const results = [],
      invResults = [],
      currDomain = location.hostname,
      urlSuffix = /\.+?[\w-]+$/.exec(currDomain);
    let totalResult = [0, false],
      black = false,
      white = false,
      match = false;
    domains.forEach((domain) => {
      const invert = domain[0] === "~";
      if (invert) domain = domain.slice(1);
      if (domain.endsWith(".*") && Array.isArray(urlSuffix)) {
        domain = domain.replace(".*", urlSuffix[0]);
      }
      const result = currDomain.endsWith(domain);
      if (invert) {
        if (result) white = true;
        invResults.push([domain.length, !result]);
      } else {
        if (result) black = true;
        results.push([domain.length, result]);
      }
    });
    if (results.length > 0 && !black) {
      match = false;
    } else if (invResults.length > 0 && !white) {
      match = true;
    } else {
      results.forEach((r) => {
        if (r[0] >= totalResult[0] && r[1]) {
          totalResult = r;
        }
      });
      invResults.forEach((r) => {
        if (r[0] >= totalResult[0] && !r[1]) {
          totalResult = r;
        }
      });
      match = totalResult[1];
    }
    return [match, results.length === 0];
  }
  function hasSome(str, arr) {
    return arr.some((word) => str.includes(word));
  }
  function ruleSpliter(rule) {
    const group = rule.match(CRRE);
    if (group) {
      const [, isDomain, place = "*", flag, sel] = group,
        type = CRFlags.indexOf(flag),
        matchResult =
          place === "*"
            ? [true, true]
            : domainChecker(place.split(isDomain ? "|" : ","));
      if (sel && matchResult[0]) {
        return {
          black: type % 2 ? "white" : "black",
          type: Math.floor(type / 2),
          place: (isDomain ? "|" : "") + place,
          generic: matchResult[1],
          sel,
        };
      }
    }
  }
  function ruleLoader(rule) {
    if (
      hasSome(rule, [
        ":matches-path(",
        ":min-text-length(",
        ":watch-attr(",
        ":-abp-properties(",
        ":matches-property(",
      ])
    )
      return;
    // 去掉开头末尾空格
    rule = rule.trim();
    // 如果 #$# 不包含 {} 就排除
    // 可以尽量排除 Snippet Filters
    if (
      /(?:\w|\*|]|^)#\$#/.test(rule) &&
      !/{\s*[a-zA-Z-]+\s*:\s*.+}\s*$/.test(rule)
    )
      return;
    // ## -> #?#
    if (
      /(?:\w|\*|]|^)#@?\$?#/.test(rule) &&
      hasSome(rule, [
        ":has(",
        ":-abp-has(",
        "[-ext-has=",
        ":has-text(",
        ":contains(",
        ":-abp-contains(",
        "[-ext-contains=",
        ":matches-css(",
        "[-ext-matches-css=",
        ":matches-css-before(",
        "[-ext-matches-css-before=",
        ":matches-css-after(",
        "[-ext-matches-css-after=",
        ":matches-attr(",
        ":nth-ancestor(",
        ":upward(",
        ":xpath(",
        ":remove()",
        ":not(",
      ])
    ) {
      rule = rule.replace(/(\w|\*|]|^)#(@?\$?)#/, "$1#$2?#");
    }
    // :style(...) 转换
    // example.com#?##id:style(color: red)
    // example.com#$?##id { color: red }
    if (rule.includes(":style(")) {
      rule = rule
        .replace(/(\w|\*|]|^)#(@?)(\??)#/, "$1#$2$$$3#")
        .replace(/:style\(\s*/, " { ")
        .replace(/\s*\)$/, " }");
    }
    return ruleSpliter(rule);
  }
  function ruleToCss(rule, preset) {
    var _a, _b;
    const isStyle = /}\s*$/.test(rule.sel);
    return [
      `/* ${rule.type}${rule.place} */ ${
        rule.sel + (!isStyle ? preset : "")
      } \n`,
      isStyle
        ? (_b =
            (_a = rule.sel.match(/^(.+?)\s*{\s*[a-zA-Z-]+\s*:\s*.+}\s*$/)) ===
              null || _a === void 0
              ? void 0
              : _a[1]) !== null && _b !== void 0
          ? _b
          : rule.sel
        : rule.sel,
    ];
  }
  function cssToAbp(css) {
    var _a;
    const flags = ["##", "#?#", "#$#", "#$?#"];
    const [, typeStr, isDomain, place, sel, style] =
      (_a = css.match(CCRE)) !== null && _a !== void 0 ? _a : [];
    if (typeStr === undefined) return null;
    const type = parseInt(typeStr);
    return [
      `${place === "*" ? "" : isDomain ? `[$domain=${place}]` : place}${
        flags[type]
      }${type >= 2 ? sel : style}`,
      type,
      style,
    ];
  }
  function downUrl(url, name) {
    const a = document.createElement("a");
    a.href = url;
    a.download = name;
    Object.assign(a.style, {
      position: "fixed",
      top: "200%",
    });
    document.body.appendChild(a);
    setTimeout(() => {
      a.click();
      a.remove();
    }, 0);
  }

  var _a, _b, _c;
  const emptyStyle = {
    needUpdate: true,
    genHideCss: "",
    genExtraCss: "",
    spcHideCss: "",
    spcExtraCss: "",
  };
  const values = {
      get black() {
        const arrStr = gmValue("get", false, "ajs_disabled_domains", "");
        return typeof arrStr == "string" && arrStr.length > 0
          ? arrStr.split(",")
          : [];
      },
      set black(v) {
        gmValue(
          "set",
          false,
          "ajs_disabled_domains",
          v === null || v === void 0 ? void 0 : v.join()
        );
      },
      get rules() {
        return gmValue("get", true, "ajs_saved_abprules", {});
      },
      set rules(v) {
        gmValue("set", true, "ajs_saved_abprules", v);
      },
      get css() {
        return gmValue(
          "get",
          true,
          `ajs_saved_styles_${location.hostname}`,
          emptyStyle
        );
      },
      set css(v) {
        gmValue("set", true, `ajs_saved_styles_${location.hostname}`, v);
      },
      get hasSave() {
        const arrStr = gmValue("get", false, "ajs_hasSave_domains", "");
        return typeof arrStr == "string" && arrStr.length > 0
          ? arrStr.split(",")
          : [];
      },
      set hasSave(v) {
        gmValue(
          "set",
          false,
          "ajs_hasSave_domains",
          v === null || v === void 0 ? void 0 : v.join()
        );
      },
      get time() {
        return gmValue("get", false, "ajs_rules_ver", "0/0/0 0:0:0");
      },
      set time(v) {
        gmValue("set", false, "ajs_rules_ver", v);
      },
      get etags() {
        return gmValue("get", true, "ajs_rules_etags", {});
      },
      set etags(v) {
        gmValue("set", true, "ajs_rules_etags", v);
      },
      get brules() {
        return gmValue("get", true, "ajs_modifier_rules", []);
      },
      set brules(v) {
        gmValue("set", true, "ajs_modifier_rules", v);
      },
      get hash() {
        return gmValue("get", false, "ajs_custom_hash", "");
      },
      set hash(v) {
        gmValue("set", false, "ajs_custom_hash", v);
      },
    },
    menus = {
      disable: {
        id: undefined,
        get text() {
          return `在此域名${data.disabled ? "启用" : "禁用"}拦截`;
        },
      },
      update: {
        id: undefined,
        get text() {
          const time = values.time;
          return data.updating
            ? "正在更新..."
            : `点击更新 ${time.slice(0, 1) === "0" ? "未知时间" : time}`;
        },
      },
      count: {
        id: undefined,
        get text() {
          var _a, _b;
          let cssCount = "";
          if ((data.appliedLevel & 1) == 0)
            cssCount += data.genHideCss + data.genExtraCss;
          if ((data.appliedLevel & 2) == 0)
            cssCount += data.spcHideCss + data.spcExtraCss;
          return data.isClean
            ? "已清空,点击刷新重新加载规则"
            : `点击清空 ${
                data.saved
                  ? (_b =
                      "CSS " +
                      ((_a = cssCount.match(/{/g)) === null || _a === void 0
                        ? void 0
                        : _a.length)) !== null && _b !== void 0
                    ? _b
                    : "未知"
                  : "规则 " +
                    data.appliedCount +
                    "/" +
                    data.allRules.split("\n").length
              }`;
        },
      },
      export: {
        id: undefined,
        text: "下载统计报告",
      },
    };
  const data = {
    disabled: values.black.includes(location.hostname),
    saved: false,
    update: true,
    updating: false,
    receivedRules: "",
    customRules:
      defaultRules +
      ((_c =
        "\n" +
        ((_b =
          (_a =
            cat.GM_info === null || cat.GM_info === void 0
              ? void 0
              : cat.GM_info.script) === null || _a === void 0
            ? void 0
            : _a.options) === null || _b === void 0
          ? void 0
          : _b.comment)) !== null && _c !== void 0
        ? _c
        : ""),
    allRules: "",
    genHideCss: "",
    genExtraCss: "",
    spcHideCss: "",
    spcExtraCss: "",
    selectors: makeRuleBox(),
    extSelectors: makeRuleBox(),
    styles: makeRuleBox(),
    extStyles: makeRuleBox(),
    bRules: [],
    appliedLevel: 0,
    appliedCount: 0,
    isFrame: cat.unsafeWindow.self !== cat.unsafeWindow.top,
    isClean: false,
    mutex: "__lemon__abp__parser__$__",
    preset: getUserConfig("css"),
    timeout: getUserConfig("timeout"),
    xTimeout: 1000,
    tryCount: getUserConfig("tryCount"),
    tryTimeout: getUserConfig("tryTimeout"),
  };
  function gmMenu(name, cb) {
    var _a;
    const id = (_a = menus[name].id) !== null && _a !== void 0 ? _a : undefined;
    if (
      typeof cat.GM_registerMenuCommand != "function" ||
      typeof cat.GM_unregisterMenuCommand != "function" ||
      data.isFrame
    )
      return;
    if (typeof id != "undefined") {
      cat.GM_unregisterMenuCommand(id);
      menus[name].id = undefined;
    }
    if (typeof cb == "function") {
      menus[name].id = cat.GM_registerMenuCommand(menus[name].text, cb);
    }
  }
  function gmValue(action, json, key, value) {
    switch (action) {
      case "get":
        try {
          const v = cat.GM_getValue(key, json ? JSON.stringify(value) : value);
          return json && typeof v == "string" ? JSON.parse(v) : v;
        } catch (error) {
          return value;
        }
      case "set":
        try {
          value === null || value === undefined
            ? cat.GM_deleteValue(key)
            : cat.GM_setValue(key, json ? JSON.stringify(value) : value);
        } catch (error) {
          cat.GM_deleteValue(key);
        }
        break;
    }
  }
  function getUserConfig(prop) {
    var _a;
    {
      return (_a = gmValue("get", false, `配置.${prop}`)) !== null &&
        _a !== void 0
        ? _a
        : userConfig[prop];
    }
  }
  function addStyle(css, pass = 0) {
    let el;
    if (pass >= data.tryCount) return;
    if (typeof cat.GM_addStyle == "function") {
      el = cat.GM_addStyle(css);
    } else {
      el = document.createElement("style");
      el.textContent = css;
      document.documentElement.appendChild(el);
    }
    if (!el || !document.documentElement.contains(el)) {
      setTimeout(() => {
        addStyle(css, pass + 1);
      }, data.tryTimeout);
    }
  }
  function promiseXhr(details) {
    return __awaiter(this, void 0, void 0, function* () {
      let loaded = false;
      return yield new Promise((resolve, reject) => {
        cat.GM_xmlhttpRequest(
          Object.assign(
            {
              onload(e) {
                loaded = true;
                resolve(e);
              },
              onabort: reject.bind(null, "abort"),
              onerror(e) {
                reject({
                  error: "error",
                  resp: e,
                });
              },
              ontimeout: reject.bind(null, "timeout"),
              onreadystatechange(e) {
                // X 浏览器超时中断
                if (
                  (e === null || e === void 0 ? void 0 : e.readyState) === 4
                ) {
                  setTimeout(() => {
                    if (!loaded)
                      reject({
                        error: "X timeout",
                        resp: e,
                      });
                  }, data.xTimeout);
                }
                // Via 浏览器超时中断,不给成功状态...
                if (
                  (e === null || e === void 0 ? void 0 : e.readyState) === 3
                ) {
                  setTimeout(() => {
                    if (!loaded)
                      reject({
                        error: "Via timeout",
                        resp: e,
                      });
                  }, data.timeout);
                }
              },
              timeout: data.timeout,
            },
            details
          )
        );
      });
    });
  }
  function getRuleFromResource(key) {
    try {
      return cat.GM_getResourceText(key);
    } catch (error) {
      return null;
    }
  }

  function saveCss() {
    var _a, _b;
    const styles = {
        needUpdate: false,
        genHideCss: data.genHideCss,
        genExtraCss: data.genExtraCss,
        spcHideCss: data.spcHideCss,
        spcExtraCss: data.spcExtraCss,
      },
      has = values.hasSave;
    values.css = styles;
    if (
      ((_b =
        (_a = values.css) === null || _a === void 0
          ? void 0
          : _a.genHideCss) === null || _b === void 0
        ? void 0
        : _b.length) === data.genHideCss.length &&
      !has.includes(location.hostname)
    ) {
      has.push(location.hostname);
      values.hasSave = has;
    }
  }
  function readCss() {
    const styles = values.css;
    if (!hasSome(Object.keys(styles), styleBoxes)) {
      values.css = emptyStyle;
      return false;
    }
    styleBoxes.forEach((sname) => {
      var _a;
      if (styles[sname].length > 0) {
        data.saved = true;
        data.update =
          (_a = styles.needUpdate) !== null && _a !== void 0 ? _a : true;
        data[sname] = styles[sname];
      }
    });
    return data.saved;
  }

  function _defineProperty(obj, key, value) {
    if (key in obj) {
      Object.defineProperty(obj, key, {
        value: value,
        enumerable: true,
        configurable: true,
        writable: true,
      });
    } else {
      obj[key] = value;
    }
    return obj;
  }
  const NODE = {
    SELECTOR_LIST: "SelectorList",
    SELECTOR: "Selector",
    REGULAR_SELECTOR: "RegularSelector",
    EXTENDED_SELECTOR: "ExtendedSelector",
    ABSOLUTE_PSEUDO_CLASS: "AbsolutePseudoClass",
    RELATIVE_PSEUDO_CLASS: "RelativePseudoClass",
  };
  class AnySelectorNode {
    constructor(type) {
      _defineProperty(this, "children", []);
      this.type = type;
    }
    addChild(child) {
      this.children.push(child);
    }
  }
  class RegularSelectorNode extends AnySelectorNode {
    constructor(value) {
      super(NODE.REGULAR_SELECTOR);
      this.value = value;
    }
  }
  class RelativePseudoClassNode extends AnySelectorNode {
    constructor(name) {
      super(NODE.RELATIVE_PSEUDO_CLASS);
      this.name = name;
    }
  }
  class AbsolutePseudoClassNode extends AnySelectorNode {
    constructor(name) {
      super(NODE.ABSOLUTE_PSEUDO_CLASS);
      _defineProperty(this, "value", "");
      this.name = name;
    }
  }
  const LEFT_SQUARE_BRACKET = "[";
  const RIGHT_SQUARE_BRACKET = "]";
  const LEFT_PARENTHESIS = "(";
  const RIGHT_PARENTHESIS = ")";
  const LEFT_CURLY_BRACKET = "{";
  const RIGHT_CURLY_BRACKET = "}";
  const BRACKET = {
    SQUARE: {
      LEFT: LEFT_SQUARE_BRACKET,
      RIGHT: RIGHT_SQUARE_BRACKET,
    },
    PARENTHESES: {
      LEFT: LEFT_PARENTHESIS,
      RIGHT: RIGHT_PARENTHESIS,
    },
    CURLY: {
      LEFT: LEFT_CURLY_BRACKET,
      RIGHT: RIGHT_CURLY_BRACKET,
    },
  };
  const SLASH = "/";
  const BACKSLASH = "\\";
  const SPACE = " ";
  const COMMA = ",";
  const DOT = ".";
  const SEMICOLON = ";";
  const COLON = ":";
  const SINGLE_QUOTE = "'";
  const DOUBLE_QUOTE = '"';
  const CARET = "^";
  const DOLLAR_SIGN = "$";
  const EQUAL_SIGN = "=";
  const TAB = "\t";
  const CARRIAGE_RETURN = "\r";
  const LINE_FEED = "\n";
  const FORM_FEED = "\f";
  const WHITE_SPACE_CHARACTERS = [
    SPACE,
    TAB,
    CARRIAGE_RETURN,
    LINE_FEED,
    FORM_FEED,
  ];
  const ASTERISK = "*";
  const ID_MARKER = "#";
  const CLASS_MARKER = DOT;
  const DESCENDANT_COMBINATOR = SPACE;
  const CHILD_COMBINATOR = ">";
  const NEXT_SIBLING_COMBINATOR = "+";
  const SUBSEQUENT_SIBLING_COMBINATOR = "~";
  const COMBINATORS = [
    DESCENDANT_COMBINATOR,
    CHILD_COMBINATOR,
    NEXT_SIBLING_COMBINATOR,
    SUBSEQUENT_SIBLING_COMBINATOR,
  ];
  const SUPPORTED_SELECTOR_MARKS = [
    LEFT_SQUARE_BRACKET,
    RIGHT_SQUARE_BRACKET,
    LEFT_PARENTHESIS,
    RIGHT_PARENTHESIS,
    LEFT_CURLY_BRACKET,
    RIGHT_CURLY_BRACKET,
    SLASH,
    BACKSLASH,
    SEMICOLON,
    COLON,
    COMMA,
    SINGLE_QUOTE,
    DOUBLE_QUOTE,
    CARET,
    DOLLAR_SIGN,
    ASTERISK,
    ID_MARKER,
    CLASS_MARKER,
    DESCENDANT_COMBINATOR,
    CHILD_COMBINATOR,
    NEXT_SIBLING_COMBINATOR,
    SUBSEQUENT_SIBLING_COMBINATOR,
    TAB,
    CARRIAGE_RETURN,
    LINE_FEED,
    FORM_FEED,
  ];
  const SUPPORTED_STYLE_DECLARATION_MARKS = [
    COLON,
    SEMICOLON,
    SINGLE_QUOTE,
    DOUBLE_QUOTE,
    BACKSLASH,
    SPACE,
    TAB,
    CARRIAGE_RETURN,
    LINE_FEED,
    FORM_FEED,
  ];
  const CONTAINS_PSEUDO = "contains";
  const HAS_TEXT_PSEUDO = "has-text";
  const ABP_CONTAINS_PSEUDO = "-abp-contains";
  const MATCHES_CSS_PSEUDO = "matches-css";
  const MATCHES_CSS_BEFORE_PSEUDO = "matches-css-before";
  const MATCHES_CSS_AFTER_PSEUDO = "matches-css-after";
  const MATCHES_ATTR_PSEUDO_CLASS_MARKER = "matches-attr";
  const MATCHES_PROPERTY_PSEUDO_CLASS_MARKER = "matches-property";
  const XPATH_PSEUDO_CLASS_MARKER = "xpath";
  const NTH_ANCESTOR_PSEUDO_CLASS_MARKER = "nth-ancestor";
  const CONTAINS_PSEUDO_NAMES = [
    CONTAINS_PSEUDO,
    HAS_TEXT_PSEUDO,
    ABP_CONTAINS_PSEUDO,
  ];
  const UPWARD_PSEUDO_CLASS_MARKER = "upward";
  const REMOVE_PSEUDO_MARKER = "remove";
  const HAS_PSEUDO_CLASS_MARKER = "has";
  const ABP_HAS_PSEUDO_CLASS_MARKER = "-abp-has";
  const HAS_PSEUDO_CLASS_MARKERS = [
    HAS_PSEUDO_CLASS_MARKER,
    ABP_HAS_PSEUDO_CLASS_MARKER,
  ];
  const IS_PSEUDO_CLASS_MARKER = "is";
  const NOT_PSEUDO_CLASS_MARKER = "not";
  const ABSOLUTE_PSEUDO_CLASSES = [
    CONTAINS_PSEUDO,
    HAS_TEXT_PSEUDO,
    ABP_CONTAINS_PSEUDO,
    MATCHES_CSS_PSEUDO,
    MATCHES_CSS_BEFORE_PSEUDO,
    MATCHES_CSS_AFTER_PSEUDO,
    MATCHES_ATTR_PSEUDO_CLASS_MARKER,
    MATCHES_PROPERTY_PSEUDO_CLASS_MARKER,
    XPATH_PSEUDO_CLASS_MARKER,
    NTH_ANCESTOR_PSEUDO_CLASS_MARKER,
    UPWARD_PSEUDO_CLASS_MARKER,
  ];
  const RELATIVE_PSEUDO_CLASSES = [
    ...HAS_PSEUDO_CLASS_MARKERS,
    IS_PSEUDO_CLASS_MARKER,
    NOT_PSEUDO_CLASS_MARKER,
  ];
  const SUPPORTED_PSEUDO_CLASSES = [
    ...ABSOLUTE_PSEUDO_CLASSES,
    ...RELATIVE_PSEUDO_CLASSES,
  ];
  const OPTIMIZATION_PSEUDO_CLASSES = [
    NOT_PSEUDO_CLASS_MARKER,
    IS_PSEUDO_CLASS_MARKER,
  ];
  const SCOPE_CSS_PSEUDO_CLASS = ":scope";
  const REGULAR_PSEUDO_ELEMENTS = {
    AFTER: "after",
    BACKDROP: "backdrop",
    BEFORE: "before",
    CUE: "cue",
    CUE_REGION: "cue-region",
    FIRST_LETTER: "first-letter",
    FIRST_LINE: "first-line",
    FILE_SELECTION_BUTTON: "file-selector-button",
    GRAMMAR_ERROR: "grammar-error",
    MARKER: "marker",
    PART: "part",
    PLACEHOLDER: "placeholder",
    SELECTION: "selection",
    SLOTTED: "slotted",
    SPELLING_ERROR: "spelling-error",
    TARGET_TEXT: "target-text",
  };
  const AT_RULE_MARKER = "@";
  const CONTENT_CSS_PROPERTY = "content";
  const PSEUDO_PROPERTY_POSITIVE_VALUE = "true";
  const DEBUG_PSEUDO_PROPERTY_GLOBAL_VALUE = "global";
  const NO_SELECTOR_ERROR_PREFIX = "Selector should be defined";
  const STYLE_ERROR_PREFIX = {
    NO_STYLE: "No style declaration found",
    NO_SELECTOR: `${NO_SELECTOR_ERROR_PREFIX} before style declaration in stylesheet`,
    INVALID_STYLE: "Invalid style declaration",
    UNCLOSED_STYLE: "Unclosed style declaration",
    NO_PROPERTY: "Missing style property in declaration",
    NO_VALUE: "Missing style value in declaration",
    NO_STYLE_OR_REMOVE:
      "Style should be declared or :remove() pseudo-class should used",
    NO_COMMENT: "Comments are not supported",
  };
  const NO_AT_RULE_ERROR_PREFIX = "At-rules are not supported";
  const REMOVE_ERROR_PREFIX = {
    INVALID_REMOVE: "Invalid :remove() pseudo-class in selector",
    NO_TARGET_SELECTOR: `${NO_SELECTOR_ERROR_PREFIX} before :remove() pseudo-class`,
    MULTIPLE_USAGE: "Pseudo-class :remove() appears more than once in selector",
    INVALID_POSITION: "Pseudo-class :remove() should be at the end of selector",
  };
  const MATCHING_ELEMENT_ERROR_PREFIX = "Error while matching element";
  const MAX_STYLE_PROTECTION_COUNT = 50;
  const REGEXP_VALID_OLD_SYNTAX =
    /\[-(?:ext)-([a-z-_]+)=(["'])((?:(?=(\\?))\4.)*?)\2\]/g;
  const INVALID_OLD_SYNTAX_MARKER = "[-ext-";
  const evaluateMatch = (match, name, quoteChar, rawValue) => {
    const re = new RegExp(`([^\\\\]|^)\\\\${quoteChar}`, "g");
    const value = rawValue.replace(re, `$1${quoteChar}`);
    return `:${name}(${value})`;
  };
  const SCOPE_MARKER_REGEXP = /\(:scope >/g;
  const SCOPE_REPLACER = "(>";
  const MATCHES_CSS_PSEUDO_ELEMENT_REGEXP = /(:matches-css)-(before|after)\(/g;
  const convertMatchesCss = (
    match,
    extendedPseudoClass,
    regularPseudoElement
  ) => {
    return `${extendedPseudoClass}${BRACKET.PARENTHESES.LEFT}${regularPseudoElement}${COMMA}`;
  };
  const normalize = (selector) => {
    const normalizedSelector = selector
      .replace(REGEXP_VALID_OLD_SYNTAX, evaluateMatch)
      .replace(SCOPE_MARKER_REGEXP, SCOPE_REPLACER)
      .replace(MATCHES_CSS_PSEUDO_ELEMENT_REGEXP, convertMatchesCss);
    if (normalizedSelector.includes(INVALID_OLD_SYNTAX_MARKER)) {
      throw new Error(
        `Invalid extended-css old syntax selector: '${selector}'`
      );
    }
    return normalizedSelector;
  };
  const convert = (rawSelector) => {
    const trimmedSelector = rawSelector.trim();
    return normalize(trimmedSelector);
  };
  const TOKEN_TYPE = {
    MARK: "mark",
    WORD: "word",
  };
  const tokenize = (input, supportedMarks) => {
    let wordBuffer = "";
    const tokens = [];
    const selectorSymbols = input.split("");
    selectorSymbols.forEach((symbol) => {
      if (supportedMarks.includes(symbol)) {
        if (wordBuffer.length > 0) {
          tokens.push({
            type: TOKEN_TYPE.WORD,
            value: wordBuffer,
          });
          wordBuffer = "";
        }
        tokens.push({
          type: TOKEN_TYPE.MARK,
          value: symbol,
        });
        return;
      }
      wordBuffer += symbol;
    });
    if (wordBuffer.length > 0) {
      tokens.push({
        type: TOKEN_TYPE.WORD,
        value: wordBuffer,
      });
    }
    return tokens;
  };
  const tokenizeSelector = (rawSelector) => {
    const selector = convert(rawSelector);
    return tokenize(selector, SUPPORTED_SELECTOR_MARKS);
  };
  const tokenizeAttribute = (attribute) => {
    return tokenize(attribute, [...SUPPORTED_SELECTOR_MARKS, EQUAL_SIGN]);
  };
  const flatten = (input) => {
    const stack = [];
    input.forEach((el) => stack.push(el));
    const res = [];
    while (stack.length) {
      const next = stack.pop();
      if (!next) {
        throw new Error("Unable to make array flat");
      }
      if (Array.isArray(next)) {
        next.forEach((el) => stack.push(el));
      } else {
        res.push(next);
      }
    }
    return res.reverse();
  };
  const getFirst = (array) => {
    return array[0];
  };
  const getLast = (array) => {
    return array[array.length - 1];
  };
  const getPrevToLast = (array) => {
    return array[array.length - 2];
  };
  const getItemByIndex = (array, index, errorMessage) => {
    const indexChild = array[index];
    if (!indexChild) {
      throw new Error(errorMessage || `No array item found by index ${index}`);
    }
    return indexChild;
  };
  const NO_REGULAR_SELECTOR_ERROR =
    "At least one of Selector node children should be RegularSelector";
  const isSelectorListNode = (astNode) => {
    return (
      (astNode === null || astNode === void 0 ? void 0 : astNode.type) ===
      NODE.SELECTOR_LIST
    );
  };
  const isSelectorNode = (astNode) => {
    return (
      (astNode === null || astNode === void 0 ? void 0 : astNode.type) ===
      NODE.SELECTOR
    );
  };
  const isRegularSelectorNode = (astNode) => {
    return (
      (astNode === null || astNode === void 0 ? void 0 : astNode.type) ===
      NODE.REGULAR_SELECTOR
    );
  };
  const isExtendedSelectorNode = (astNode) => {
    return astNode.type === NODE.EXTENDED_SELECTOR;
  };
  const isAbsolutePseudoClassNode = (astNode) => {
    return (
      (astNode === null || astNode === void 0 ? void 0 : astNode.type) ===
      NODE.ABSOLUTE_PSEUDO_CLASS
    );
  };
  const isRelativePseudoClassNode = (astNode) => {
    return (
      (astNode === null || astNode === void 0 ? void 0 : astNode.type) ===
      NODE.RELATIVE_PSEUDO_CLASS
    );
  };
  const getNodeName = (astNode) => {
    if (astNode === null) {
      throw new Error("Ast node should be defined");
    }
    if (
      !isAbsolutePseudoClassNode(astNode) &&
      !isRelativePseudoClassNode(astNode)
    ) {
      throw new Error(
        "Only AbsolutePseudoClass or RelativePseudoClass ast node can have a name"
      );
    }
    if (!astNode.name) {
      throw new Error("Extended pseudo-class should have a name");
    }
    return astNode.name;
  };
  const getNodeValue = (astNode, errorMessage) => {
    if (astNode === null) {
      throw new Error("Ast node should be defined");
    }
    if (
      !isRegularSelectorNode(astNode) &&
      !isAbsolutePseudoClassNode(astNode)
    ) {
      throw new Error(
        "Only RegularSelector ot AbsolutePseudoClass ast node can have a value"
      );
    }
    if (!astNode.value) {
      throw new Error(
        errorMessage ||
          "Ast RegularSelector ot AbsolutePseudoClass node should have a value"
      );
    }
    return astNode.value;
  };
  const getRegularSelectorNodes = (children) => {
    return children.filter(isRegularSelectorNode);
  };
  const getFirstRegularChild = (children, errorMessage) => {
    const regularSelectorNodes = getRegularSelectorNodes(children);
    const firstRegularSelectorNode = getFirst(regularSelectorNodes);
    if (!firstRegularSelectorNode) {
      throw new Error(errorMessage || NO_REGULAR_SELECTOR_ERROR);
    }
    return firstRegularSelectorNode;
  };
  const getLastRegularChild = (children) => {
    const regularSelectorNodes = getRegularSelectorNodes(children);
    const lastRegularSelectorNode = getLast(regularSelectorNodes);
    if (!lastRegularSelectorNode) {
      throw new Error(NO_REGULAR_SELECTOR_ERROR);
    }
    return lastRegularSelectorNode;
  };
  const getNodeOnlyChild = (node, errorMessage) => {
    if (node.children.length !== 1) {
      throw new Error(errorMessage);
    }
    const onlyChild = getFirst(node.children);
    if (!onlyChild) {
      throw new Error(errorMessage);
    }
    return onlyChild;
  };
  const getPseudoClassNode = (extendedSelectorNode) => {
    return getNodeOnlyChild(
      extendedSelectorNode,
      "Extended selector should be specified"
    );
  };
  const getRelativeSelectorListNode = (pseudoClassNode) => {
    if (!isRelativePseudoClassNode(pseudoClassNode)) {
      throw new Error(
        "Only RelativePseudoClass node can have relative SelectorList node as child"
      );
    }
    return getNodeOnlyChild(
      pseudoClassNode,
      `Missing arg for :${getNodeName(pseudoClassNode)}() pseudo-class`
    );
  };
  const ATTRIBUTE_CASE_INSENSITIVE_FLAG = "i";
  const POSSIBLE_MARKS_BEFORE_REGEXP = {
    COMMON: [
      BRACKET.PARENTHESES.LEFT,
      SINGLE_QUOTE,
      DOUBLE_QUOTE,
      EQUAL_SIGN,
      DOT,
      COLON,
      SPACE,
    ],
    CONTAINS: [BRACKET.PARENTHESES.LEFT, SINGLE_QUOTE, DOUBLE_QUOTE],
  };
  const isSupportedPseudoClass = (tokenValue) => {
    return SUPPORTED_PSEUDO_CLASSES.includes(tokenValue);
  };
  const isOptimizationPseudoClass = (name) => {
    return OPTIMIZATION_PSEUDO_CLASSES.includes(name);
  };
  const doesRegularContinueAfterSpace = (nextTokenType, nextTokenValue) => {
    if (!nextTokenType || !nextTokenValue) {
      return false;
    }
    return (
      COMBINATORS.includes(nextTokenValue) ||
      nextTokenType === TOKEN_TYPE.WORD ||
      nextTokenValue === ASTERISK ||
      nextTokenValue === ID_MARKER ||
      nextTokenValue === CLASS_MARKER ||
      nextTokenValue === COLON ||
      nextTokenValue === SINGLE_QUOTE ||
      nextTokenValue === DOUBLE_QUOTE ||
      nextTokenValue === BRACKET.SQUARE.LEFT
    );
  };
  const isRegexpOpening = (context, prevTokenValue, bufferNodeValue) => {
    const lastExtendedPseudoClassName = getLast(
      context.extendedPseudoNamesStack
    );
    if (!lastExtendedPseudoClassName) {
      throw new Error(
        "Regexp pattern allowed only in arg of extended pseudo-class"
      );
    }
    if (CONTAINS_PSEUDO_NAMES.includes(lastExtendedPseudoClassName)) {
      return POSSIBLE_MARKS_BEFORE_REGEXP.CONTAINS.includes(prevTokenValue);
    }
    if (
      prevTokenValue === SLASH &&
      lastExtendedPseudoClassName !== XPATH_PSEUDO_CLASS_MARKER
    ) {
      const rawArgDesc = bufferNodeValue
        ? `in arg part: '${bufferNodeValue}'`
        : "arg";
      throw new Error(
        `Invalid regexp pattern for :${lastExtendedPseudoClassName}() pseudo-class ${rawArgDesc}`
      );
    }
    return POSSIBLE_MARKS_BEFORE_REGEXP.COMMON.includes(prevTokenValue);
  };
  const isAttributeOpening = (tokenValue, prevTokenValue) => {
    return tokenValue === BRACKET.SQUARE.LEFT && prevTokenValue !== BACKSLASH;
  };
  const isAttributeClosing = (context) => {
    var _getPrevToLast;
    if (!context.isAttributeBracketsOpen) {
      return false;
    }
    const noSpaceAttr = context.attributeBuffer.split(SPACE).join("");
    const attrTokens = tokenizeAttribute(noSpaceAttr);
    const firstAttrToken = getFirst(attrTokens);
    const firstAttrTokenType =
      firstAttrToken === null || firstAttrToken === void 0
        ? void 0
        : firstAttrToken.type;
    const firstAttrTokenValue =
      firstAttrToken === null || firstAttrToken === void 0
        ? void 0
        : firstAttrToken.value;
    if (
      firstAttrTokenType === TOKEN_TYPE.MARK &&
      firstAttrTokenValue !== BACKSLASH
    ) {
      throw new Error(
        `'[${context.attributeBuffer}]' is not a valid attribute due to '${firstAttrTokenValue}' at start of it`
      );
    }
    const lastAttrToken = getLast(attrTokens);
    const lastAttrTokenType =
      lastAttrToken === null || lastAttrToken === void 0
        ? void 0
        : lastAttrToken.type;
    const lastAttrTokenValue =
      lastAttrToken === null || lastAttrToken === void 0
        ? void 0
        : lastAttrToken.value;
    if (lastAttrTokenValue === EQUAL_SIGN) {
      throw new Error(
        `'[${context.attributeBuffer}]' is not a valid attribute due to '${EQUAL_SIGN}'`
      );
    }
    const equalSignIndex = attrTokens.findIndex((token) => {
      return token.type === TOKEN_TYPE.MARK && token.value === EQUAL_SIGN;
    });
    const prevToLastAttrTokenValue =
      (_getPrevToLast = getPrevToLast(attrTokens)) === null ||
      _getPrevToLast === void 0
        ? void 0
        : _getPrevToLast.value;
    if (equalSignIndex === -1) {
      if (lastAttrTokenType === TOKEN_TYPE.WORD) {
        return true;
      }
      return (
        prevToLastAttrTokenValue === BACKSLASH &&
        (lastAttrTokenValue === DOUBLE_QUOTE ||
          lastAttrTokenValue === SINGLE_QUOTE)
      );
    }
    const nextToEqualSignToken = getItemByIndex(attrTokens, equalSignIndex + 1);
    const nextToEqualSignTokenValue = nextToEqualSignToken.value;
    const isAttrValueQuote =
      nextToEqualSignTokenValue === SINGLE_QUOTE ||
      nextToEqualSignTokenValue === DOUBLE_QUOTE;
    if (!isAttrValueQuote) {
      if (lastAttrTokenType === TOKEN_TYPE.WORD) {
        return true;
      }
      throw new Error(
        `'[${context.attributeBuffer}]' is not a valid attribute`
      );
    }
    if (
      lastAttrTokenType === TOKEN_TYPE.WORD &&
      (lastAttrTokenValue === null || lastAttrTokenValue === void 0
        ? void 0
        : lastAttrTokenValue.toLocaleLowerCase()) ===
        ATTRIBUTE_CASE_INSENSITIVE_FLAG
    ) {
      return prevToLastAttrTokenValue === nextToEqualSignTokenValue;
    }
    return lastAttrTokenValue === nextToEqualSignTokenValue;
  };
  const isWhiteSpaceChar = (tokenValue) => {
    if (!tokenValue) {
      return false;
    }
    return WHITE_SPACE_CHARACTERS.includes(tokenValue);
  };
  const isAbsolutePseudoClass = (str) => {
    return ABSOLUTE_PSEUDO_CLASSES.includes(str);
  };
  const isRelativePseudoClass = (str) => {
    return RELATIVE_PSEUDO_CLASSES.includes(str);
  };
  const getBufferNode = (context) => {
    if (context.pathToBufferNode.length === 0) {
      return null;
    }
    return getLast(context.pathToBufferNode) || null;
  };
  const getBufferNodeParent = (context) => {
    if (context.pathToBufferNode.length < 2) {
      return null;
    }
    return getPrevToLast(context.pathToBufferNode) || null;
  };
  const getContextLastRegularSelectorNode = (context) => {
    const bufferNode = getBufferNode(context);
    if (!bufferNode) {
      throw new Error("No bufferNode found");
    }
    if (!isSelectorNode(bufferNode)) {
      throw new Error("Unsupported bufferNode type");
    }
    const lastRegularSelectorNode = getLastRegularChild(bufferNode.children);
    context.pathToBufferNode.push(lastRegularSelectorNode);
    return lastRegularSelectorNode;
  };
  const updateBufferNode = (context, tokenValue) => {
    const bufferNode = getBufferNode(context);
    if (bufferNode === null) {
      throw new Error("No bufferNode to update");
    }
    if (isAbsolutePseudoClassNode(bufferNode)) {
      bufferNode.value += tokenValue;
    } else if (isRegularSelectorNode(bufferNode)) {
      bufferNode.value += tokenValue;
      if (context.isAttributeBracketsOpen) {
        context.attributeBuffer += tokenValue;
      }
    } else {
      throw new Error(
        `${bufferNode.type} node cannot be updated. Only RegularSelector and AbsolutePseudoClass are supported`
      );
    }
  };
  const addSelectorListNode = (context) => {
    const selectorListNode = new AnySelectorNode(NODE.SELECTOR_LIST);
    context.ast = selectorListNode;
    context.pathToBufferNode.push(selectorListNode);
  };
  const addAstNodeByType = function (context, type) {
    let tokenValue =
      arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "";
    const bufferNode = getBufferNode(context);
    if (bufferNode === null) {
      throw new Error("No buffer node");
    }
    let node;
    if (type === NODE.REGULAR_SELECTOR) {
      node = new RegularSelectorNode(tokenValue);
    } else if (type === NODE.ABSOLUTE_PSEUDO_CLASS) {
      node = new AbsolutePseudoClassNode(tokenValue);
    } else if (type === NODE.RELATIVE_PSEUDO_CLASS) {
      node = new RelativePseudoClassNode(tokenValue);
    } else {
      node = new AnySelectorNode(type);
    }
    bufferNode.addChild(node);
    context.pathToBufferNode.push(node);
  };
  const initAst = (context, tokenValue) => {
    addSelectorListNode(context);
    addAstNodeByType(context, NODE.SELECTOR);
    addAstNodeByType(context, NODE.REGULAR_SELECTOR, tokenValue);
  };
  const initRelativeSubtree = function (context) {
    let tokenValue =
      arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "";
    addAstNodeByType(context, NODE.SELECTOR_LIST);
    addAstNodeByType(context, NODE.SELECTOR);
    addAstNodeByType(context, NODE.REGULAR_SELECTOR, tokenValue);
  };
  const upToClosest = (context, parentType) => {
    for (let i = context.pathToBufferNode.length - 1; i >= 0; i -= 1) {
      var _context$pathToBuffer;
      if (
        ((_context$pathToBuffer = context.pathToBufferNode[i]) === null ||
        _context$pathToBuffer === void 0
          ? void 0
          : _context$pathToBuffer.type) === parentType
      ) {
        context.pathToBufferNode = context.pathToBufferNode.slice(0, i + 1);
        break;
      }
    }
  };
  const getUpdatedBufferNode = (context) => {
    const bufferNode = getBufferNode(context);
    if (
      bufferNode &&
      isSelectorListNode(bufferNode) &&
      isRelativePseudoClassNode(getBufferNodeParent(context))
    ) {
      return bufferNode;
    }
    upToClosest(context, NODE.SELECTOR);
    const selectorNode = getBufferNode(context);
    if (!selectorNode) {
      throw new Error(
        "No SelectorNode, impossible to continue selector parsing by ExtendedCss"
      );
    }
    const lastSelectorNodeChild = getLast(selectorNode.children);
    const hasExtended =
      lastSelectorNodeChild &&
      isExtendedSelectorNode(lastSelectorNodeChild) &&
      context.standardPseudoBracketsStack.length === 0;
    const supposedPseudoClassNode =
      hasExtended && getFirst(lastSelectorNodeChild.children);
    let newNeededBufferNode = selectorNode;
    if (supposedPseudoClassNode) {
      const lastExtendedPseudoName =
        hasExtended && supposedPseudoClassNode.name;
      const isLastExtendedNameRelative =
        lastExtendedPseudoName && isRelativePseudoClass(lastExtendedPseudoName);
      const isLastExtendedNameAbsolute =
        lastExtendedPseudoName && isAbsolutePseudoClass(lastExtendedPseudoName);
      const hasRelativeExtended =
        isLastExtendedNameRelative &&
        context.extendedPseudoBracketsStack.length > 0 &&
        context.extendedPseudoBracketsStack.length ===
          context.extendedPseudoNamesStack.length;
      const hasAbsoluteExtended =
        isLastExtendedNameAbsolute &&
        lastExtendedPseudoName === getLast(context.extendedPseudoNamesStack);
      if (hasRelativeExtended) {
        context.pathToBufferNode.push(lastSelectorNodeChild);
        newNeededBufferNode = supposedPseudoClassNode;
      } else if (hasAbsoluteExtended) {
        context.pathToBufferNode.push(lastSelectorNodeChild);
        newNeededBufferNode = supposedPseudoClassNode;
      }
    } else if (hasExtended) {
      newNeededBufferNode = selectorNode;
    } else {
      newNeededBufferNode = getContextLastRegularSelectorNode(context);
    }
    context.pathToBufferNode.push(newNeededBufferNode);
    return newNeededBufferNode;
  };
  const handleNextTokenOnColon = (
    context,
    selector,
    tokenValue,
    nextTokenValue,
    nextToNextTokenValue
  ) => {
    if (!nextTokenValue) {
      throw new Error(
        `Invalid colon ':' at the end of selector: '${selector}'`
      );
    }
    if (!isSupportedPseudoClass(nextTokenValue.toLowerCase())) {
      if (nextTokenValue.toLowerCase() === REMOVE_PSEUDO_MARKER) {
        throw new Error(`${REMOVE_ERROR_PREFIX.INVALID_REMOVE}: '${selector}'`);
      }
      updateBufferNode(context, tokenValue);
      if (
        nextToNextTokenValue &&
        nextToNextTokenValue === BRACKET.PARENTHESES.LEFT &&
        !context.isAttributeBracketsOpen
      ) {
        context.standardPseudoNamesStack.push(nextTokenValue);
      }
    } else {
      if (
        HAS_PSEUDO_CLASS_MARKERS.includes(nextTokenValue) &&
        context.standardPseudoNamesStack.length > 0
      ) {
        throw new Error(
          `Usage of :${nextTokenValue}() pseudo-class is not allowed inside regular pseudo: '${getLast(
            context.standardPseudoNamesStack
          )}'`
        );
      } else {
        upToClosest(context, NODE.SELECTOR);
        addAstNodeByType(context, NODE.EXTENDED_SELECTOR);
      }
    }
  };
  const IS_OR_NOT_PSEUDO_SELECTING_ROOT = `html ${ASTERISK}`;
  const hasExtendedSelector = (selectorList) => {
    return selectorList.children.some((selectorNode) => {
      return selectorNode.children.some((selectorNodeChild) => {
        return isExtendedSelectorNode(selectorNodeChild);
      });
    });
  };
  const selectorListOfRegularsToString = (selectorList) => {
    const standardCssSelectors = selectorList.children.map((selectorNode) => {
      const selectorOnlyChild = getNodeOnlyChild(
        selectorNode,
        "Ast Selector node should have RegularSelector node"
      );
      return getNodeValue(selectorOnlyChild);
    });
    return standardCssSelectors.join(`${COMMA}${SPACE}`);
  };
  const updateNodeChildren = (node, newChildren) => {
    node.children = newChildren;
    return node;
  };
  const shouldOptimizeExtendedSelector = (currExtendedSelectorNode) => {
    if (currExtendedSelectorNode === null) {
      return false;
    }
    const extendedPseudoClassNode = getPseudoClassNode(
      currExtendedSelectorNode
    );
    const pseudoName = getNodeName(extendedPseudoClassNode);
    if (isAbsolutePseudoClass(pseudoName)) {
      return false;
    }
    const relativeSelectorList = getRelativeSelectorListNode(
      extendedPseudoClassNode
    );
    const innerSelectorNodes = relativeSelectorList.children;
    if (isOptimizationPseudoClass(pseudoName)) {
      const areAllSelectorNodeChildrenRegular = innerSelectorNodes.every(
        (selectorNode) => {
          try {
            const selectorOnlyChild = getNodeOnlyChild(
              selectorNode,
              "Selector node should have RegularSelector"
            );
            return isRegularSelectorNode(selectorOnlyChild);
          } catch (e) {
            return false;
          }
        }
      );
      if (areAllSelectorNodeChildrenRegular) {
        return true;
      }
    }
    return innerSelectorNodes.some((selectorNode) => {
      return selectorNode.children.some((selectorNodeChild) => {
        if (!isExtendedSelectorNode(selectorNodeChild)) {
          return false;
        }
        return shouldOptimizeExtendedSelector(selectorNodeChild);
      });
    });
  };
  const getOptimizedExtendedSelector = (
    currExtendedSelectorNode,
    prevRegularSelectorNode
  ) => {
    if (!currExtendedSelectorNode) {
      return null;
    }
    const extendedPseudoClassNode = getPseudoClassNode(
      currExtendedSelectorNode
    );
    const relativeSelectorList = getRelativeSelectorListNode(
      extendedPseudoClassNode
    );
    const hasInnerExtendedSelector = hasExtendedSelector(relativeSelectorList);
    if (!hasInnerExtendedSelector) {
      const relativeSelectorListStr =
        selectorListOfRegularsToString(relativeSelectorList);
      const pseudoName = getNodeName(extendedPseudoClassNode);
      const optimizedExtendedStr = `${COLON}${pseudoName}${BRACKET.PARENTHESES.LEFT}${relativeSelectorListStr}${BRACKET.PARENTHESES.RIGHT}`;
      prevRegularSelectorNode.value = `${getNodeValue(
        prevRegularSelectorNode
      )}${optimizedExtendedStr}`;
      return null;
    }
    const optimizedRelativeSelectorList =
      optimizeSelectorListNode(relativeSelectorList);
    const optimizedExtendedPseudoClassNode = updateNodeChildren(
      extendedPseudoClassNode,
      [optimizedRelativeSelectorList]
    );
    return updateNodeChildren(currExtendedSelectorNode, [
      optimizedExtendedPseudoClassNode,
    ]);
  };
  const optimizeCurrentRegularSelector = (current, previous) => {
    previous.value = `${getNodeValue(previous)}${SPACE}${getNodeValue(
      current
    )}`;
  };
  const optimizeSelectorNode = (selectorNode) => {
    const rawSelectorNodeChildren = selectorNode.children;
    const optimizedChildrenList = [];
    let currentIndex = 0;
    while (currentIndex < rawSelectorNodeChildren.length) {
      const currentChild = getItemByIndex(
        rawSelectorNodeChildren,
        currentIndex,
        "currentChild should be specified"
      );
      if (currentIndex === 0) {
        optimizedChildrenList.push(currentChild);
      } else {
        const prevRegularChild = getLastRegularChild(optimizedChildrenList);
        if (isExtendedSelectorNode(currentChild)) {
          let optimizedExtendedSelector = null;
          let isOptimizationNeeded =
            shouldOptimizeExtendedSelector(currentChild);
          optimizedExtendedSelector = currentChild;
          while (isOptimizationNeeded) {
            optimizedExtendedSelector = getOptimizedExtendedSelector(
              optimizedExtendedSelector,
              prevRegularChild
            );
            isOptimizationNeeded = shouldOptimizeExtendedSelector(
              optimizedExtendedSelector
            );
          }
          if (optimizedExtendedSelector !== null) {
            optimizedChildrenList.push(optimizedExtendedSelector);
            const optimizedPseudoClass = getPseudoClassNode(
              optimizedExtendedSelector
            );
            const optimizedPseudoName = getNodeName(optimizedPseudoClass);
            if (
              getNodeValue(prevRegularChild) === ASTERISK &&
              isOptimizationPseudoClass(optimizedPseudoName)
            ) {
              prevRegularChild.value = IS_OR_NOT_PSEUDO_SELECTING_ROOT;
            }
          }
        } else if (isRegularSelectorNode(currentChild)) {
          const lastOptimizedChild = getLast(optimizedChildrenList) || null;
          if (isRegularSelectorNode(lastOptimizedChild)) {
            optimizeCurrentRegularSelector(currentChild, prevRegularChild);
          }
        }
      }
      currentIndex += 1;
    }
    return updateNodeChildren(selectorNode, optimizedChildrenList);
  };
  const optimizeSelectorListNode = (selectorListNode) => {
    return updateNodeChildren(
      selectorListNode,
      selectorListNode.children.map((s) => optimizeSelectorNode(s))
    );
  };
  const optimizeAst = (ast) => {
    return optimizeSelectorListNode(ast);
  };
  const XPATH_PSEUDO_SELECTING_ROOT = "body";
  const NO_WHITESPACE_ERROR_PREFIX =
    "No white space is allowed before or after extended pseudo-class name in selector";
  const parse = (selector) => {
    const tokens = tokenizeSelector(selector);
    const context = {
      ast: null,
      pathToBufferNode: [],
      extendedPseudoNamesStack: [],
      extendedPseudoBracketsStack: [],
      standardPseudoNamesStack: [],
      standardPseudoBracketsStack: [],
      isAttributeBracketsOpen: false,
      attributeBuffer: "",
      isRegexpOpen: false,
      shouldOptimize: false,
    };
    let i = 0;
    while (i < tokens.length) {
      const token = tokens[i];
      if (!token) {
        break;
      }
      const { type: tokenType, value: tokenValue } = token;
      const nextToken = tokens[i + 1];
      const nextTokenType =
        nextToken === null || nextToken === void 0 ? void 0 : nextToken.type;
      const nextTokenValue =
        nextToken === null || nextToken === void 0 ? void 0 : nextToken.value;
      const nextToNextToken = tokens[i + 2];
      const nextToNextTokenValue =
        nextToNextToken === null || nextToNextToken === void 0
          ? void 0
          : nextToNextToken.value;
      const previousToken = tokens[i - 1];
      const prevTokenType =
        previousToken === null || previousToken === void 0
          ? void 0
          : previousToken.type;
      const prevTokenValue =
        previousToken === null || previousToken === void 0
          ? void 0
          : previousToken.value;
      const previousToPreviousToken = tokens[i - 2];
      const prevToPrevTokenValue =
        previousToPreviousToken === null || previousToPreviousToken === void 0
          ? void 0
          : previousToPreviousToken.value;
      let bufferNode = getBufferNode(context);
      switch (tokenType) {
        case TOKEN_TYPE.WORD:
          if (bufferNode === null) {
            initAst(context, tokenValue);
          } else if (isSelectorListNode(bufferNode)) {
            addAstNodeByType(context, NODE.SELECTOR);
            addAstNodeByType(context, NODE.REGULAR_SELECTOR, tokenValue);
          } else if (isRegularSelectorNode(bufferNode)) {
            updateBufferNode(context, tokenValue);
          } else if (isExtendedSelectorNode(bufferNode)) {
            if (
              isWhiteSpaceChar(nextTokenValue) &&
              nextToNextTokenValue === BRACKET.PARENTHESES.LEFT
            ) {
              throw new Error(`${NO_WHITESPACE_ERROR_PREFIX}: '${selector}'`);
            }
            const lowerCaseTokenValue = tokenValue.toLowerCase();
            context.extendedPseudoNamesStack.push(lowerCaseTokenValue);
            if (isAbsolutePseudoClass(lowerCaseTokenValue)) {
              addAstNodeByType(
                context,
                NODE.ABSOLUTE_PSEUDO_CLASS,
                lowerCaseTokenValue
              );
            } else {
              addAstNodeByType(
                context,
                NODE.RELATIVE_PSEUDO_CLASS,
                lowerCaseTokenValue
              );
              if (isOptimizationPseudoClass(lowerCaseTokenValue)) {
                context.shouldOptimize = true;
              }
            }
          } else if (isAbsolutePseudoClassNode(bufferNode)) {
            updateBufferNode(context, tokenValue);
          } else if (isRelativePseudoClassNode(bufferNode)) {
            initRelativeSubtree(context, tokenValue);
          }
          break;
        case TOKEN_TYPE.MARK:
          switch (tokenValue) {
            case COMMA:
              if (
                !bufferNode ||
                (typeof bufferNode !== "undefined" && !nextTokenValue)
              ) {
                throw new Error(`'${selector}' is not a valid selector`);
              } else if (isRegularSelectorNode(bufferNode)) {
                if (context.isAttributeBracketsOpen) {
                  updateBufferNode(context, tokenValue);
                } else {
                  upToClosest(context, NODE.SELECTOR_LIST);
                }
              } else if (isAbsolutePseudoClassNode(bufferNode)) {
                updateBufferNode(context, tokenValue);
              } else if (isSelectorNode(bufferNode)) {
                upToClosest(context, NODE.SELECTOR_LIST);
              }
              break;
            case SPACE:
              if (
                isRegularSelectorNode(bufferNode) &&
                !context.isAttributeBracketsOpen
              ) {
                bufferNode = getUpdatedBufferNode(context);
              }
              if (isRegularSelectorNode(bufferNode)) {
                if (
                  !context.isAttributeBracketsOpen &&
                  ((prevTokenValue === COLON &&
                    nextTokenType === TOKEN_TYPE.WORD) ||
                    (prevTokenType === TOKEN_TYPE.WORD &&
                      nextTokenValue === BRACKET.PARENTHESES.LEFT))
                ) {
                  throw new Error(`'${selector}' is not a valid selector`);
                }
                if (
                  !nextTokenValue ||
                  doesRegularContinueAfterSpace(
                    nextTokenType,
                    nextTokenValue
                  ) ||
                  context.isAttributeBracketsOpen
                ) {
                  updateBufferNode(context, tokenValue);
                }
              }
              if (isAbsolutePseudoClassNode(bufferNode)) {
                updateBufferNode(context, tokenValue);
              }
              if (isRelativePseudoClassNode(bufferNode)) {
                initRelativeSubtree(context);
              }
              if (isSelectorNode(bufferNode)) {
                if (
                  doesRegularContinueAfterSpace(nextTokenType, nextTokenValue)
                ) {
                  addAstNodeByType(context, NODE.REGULAR_SELECTOR);
                }
              }
              break;
            case DESCENDANT_COMBINATOR:
            case CHILD_COMBINATOR:
            case NEXT_SIBLING_COMBINATOR:
            case SUBSEQUENT_SIBLING_COMBINATOR:
            case SEMICOLON:
            case SLASH:
            case BACKSLASH:
            case SINGLE_QUOTE:
            case DOUBLE_QUOTE:
            case CARET:
            case DOLLAR_SIGN:
            case BRACKET.CURLY.LEFT:
            case BRACKET.CURLY.RIGHT:
            case ASTERISK:
            case ID_MARKER:
            case CLASS_MARKER:
            case BRACKET.SQUARE.LEFT:
              if (COMBINATORS.includes(tokenValue)) {
                if (bufferNode === null) {
                  throw new Error(`'${selector}' is not a valid selector`);
                }
                bufferNode = getUpdatedBufferNode(context);
              }
              if (bufferNode === null) {
                initAst(context, tokenValue);
                if (isAttributeOpening(tokenValue, prevTokenValue)) {
                  context.isAttributeBracketsOpen = true;
                }
              } else if (isRegularSelectorNode(bufferNode)) {
                if (
                  tokenValue === BRACKET.CURLY.LEFT &&
                  !(context.isAttributeBracketsOpen || context.isRegexpOpen)
                ) {
                  throw new Error(`'${selector}' is not a valid selector`);
                }
                updateBufferNode(context, tokenValue);
                if (isAttributeOpening(tokenValue, prevTokenValue)) {
                  context.isAttributeBracketsOpen = true;
                }
              } else if (isAbsolutePseudoClassNode(bufferNode)) {
                updateBufferNode(context, tokenValue);
                if (
                  tokenValue === SLASH &&
                  context.extendedPseudoNamesStack.length > 0
                ) {
                  if (
                    prevTokenValue === SLASH &&
                    prevToPrevTokenValue === BACKSLASH
                  ) {
                    context.isRegexpOpen = false;
                  } else if (prevTokenValue && prevTokenValue !== BACKSLASH) {
                    if (
                      isRegexpOpening(
                        context,
                        prevTokenValue,
                        getNodeValue(bufferNode)
                      )
                    ) {
                      context.isRegexpOpen = !context.isRegexpOpen;
                    } else {
                      context.isRegexpOpen = false;
                    }
                  }
                }
              } else if (isRelativePseudoClassNode(bufferNode)) {
                initRelativeSubtree(context, tokenValue);
                if (isAttributeOpening(tokenValue, prevTokenValue)) {
                  context.isAttributeBracketsOpen = true;
                }
              } else if (isSelectorNode(bufferNode)) {
                if (COMBINATORS.includes(tokenValue)) {
                  addAstNodeByType(context, NODE.REGULAR_SELECTOR, tokenValue);
                } else if (!context.isRegexpOpen) {
                  bufferNode = getContextLastRegularSelectorNode(context);
                  updateBufferNode(context, tokenValue);
                  if (isAttributeOpening(tokenValue, prevTokenValue)) {
                    context.isAttributeBracketsOpen = true;
                  }
                }
              } else if (isSelectorListNode(bufferNode)) {
                addAstNodeByType(context, NODE.SELECTOR);
                addAstNodeByType(context, NODE.REGULAR_SELECTOR, tokenValue);
                if (isAttributeOpening(tokenValue, prevTokenValue)) {
                  context.isAttributeBracketsOpen = true;
                }
              }
              break;
            case BRACKET.SQUARE.RIGHT:
              if (isRegularSelectorNode(bufferNode)) {
                if (
                  !context.isAttributeBracketsOpen &&
                  prevTokenValue !== BACKSLASH
                ) {
                  throw new Error(
                    `'${selector}' is not a valid selector due to '${tokenValue}' after '${getNodeValue(
                      bufferNode
                    )}'`
                  );
                }
                if (isAttributeClosing(context)) {
                  context.isAttributeBracketsOpen = false;
                  context.attributeBuffer = "";
                }
                updateBufferNode(context, tokenValue);
              }
              if (isAbsolutePseudoClassNode(bufferNode)) {
                updateBufferNode(context, tokenValue);
              }
              break;
            case COLON:
              if (
                isWhiteSpaceChar(nextTokenValue) &&
                nextToNextTokenValue &&
                SUPPORTED_PSEUDO_CLASSES.includes(nextToNextTokenValue)
              ) {
                throw new Error(`${NO_WHITESPACE_ERROR_PREFIX}: '${selector}'`);
              }
              if (bufferNode === null) {
                if (nextTokenValue === XPATH_PSEUDO_CLASS_MARKER) {
                  initAst(context, XPATH_PSEUDO_SELECTING_ROOT);
                } else if (
                  nextTokenValue === UPWARD_PSEUDO_CLASS_MARKER ||
                  nextTokenValue === NTH_ANCESTOR_PSEUDO_CLASS_MARKER
                ) {
                  throw new Error(
                    `${NO_SELECTOR_ERROR_PREFIX} before :${nextTokenValue}() pseudo-class`
                  );
                } else {
                  initAst(context, ASTERISK);
                }
                bufferNode = getBufferNode(context);
              }
              if (isSelectorListNode(bufferNode)) {
                addAstNodeByType(context, NODE.SELECTOR);
                addAstNodeByType(context, NODE.REGULAR_SELECTOR);
                bufferNode = getBufferNode(context);
              }
              if (isRegularSelectorNode(bufferNode)) {
                if (
                  (prevTokenValue && COMBINATORS.includes(prevTokenValue)) ||
                  prevTokenValue === COMMA
                ) {
                  updateBufferNode(context, ASTERISK);
                }
                handleNextTokenOnColon(
                  context,
                  selector,
                  tokenValue,
                  nextTokenValue,
                  nextToNextTokenValue
                );
              }
              if (isSelectorNode(bufferNode)) {
                if (!nextTokenValue) {
                  throw new Error(
                    `Invalid colon ':' at the end of selector: '${selector}'`
                  );
                }
                if (isSupportedPseudoClass(nextTokenValue.toLowerCase())) {
                  addAstNodeByType(context, NODE.EXTENDED_SELECTOR);
                } else if (
                  nextTokenValue.toLowerCase() === REMOVE_PSEUDO_MARKER
                ) {
                  throw new Error(
                    `${REMOVE_ERROR_PREFIX.INVALID_REMOVE}: '${selector}'`
                  );
                } else {
                  bufferNode = getContextLastRegularSelectorNode(context);
                  handleNextTokenOnColon(
                    context,
                    selector,
                    tokenValue,
                    nextTokenType,
                    nextToNextTokenValue
                  );
                }
              }
              if (isAbsolutePseudoClassNode(bufferNode)) {
                if (
                  getNodeName(bufferNode) === XPATH_PSEUDO_CLASS_MARKER &&
                  nextTokenValue &&
                  SUPPORTED_PSEUDO_CLASSES.includes(nextTokenValue) &&
                  nextToNextTokenValue === BRACKET.PARENTHESES.LEFT
                ) {
                  throw new Error(
                    `:xpath() pseudo-class should be the last in selector: '${selector}'`
                  );
                }
                updateBufferNode(context, tokenValue);
              }
              if (isRelativePseudoClassNode(bufferNode)) {
                if (!nextTokenValue) {
                  throw new Error(
                    `Invalid pseudo-class arg at the end of selector: '${selector}'`
                  );
                }
                initRelativeSubtree(context, ASTERISK);
                if (!isSupportedPseudoClass(nextTokenValue.toLowerCase())) {
                  updateBufferNode(context, tokenValue);
                  if (nextToNextTokenValue === BRACKET.PARENTHESES.LEFT) {
                    context.standardPseudoNamesStack.push(nextTokenValue);
                  }
                } else {
                  upToClosest(context, NODE.SELECTOR);
                  addAstNodeByType(context, NODE.EXTENDED_SELECTOR);
                }
              }
              break;
            case BRACKET.PARENTHESES.LEFT:
              if (isAbsolutePseudoClassNode(bufferNode)) {
                if (
                  getNodeName(bufferNode) !== XPATH_PSEUDO_CLASS_MARKER &&
                  context.isRegexpOpen
                ) {
                  updateBufferNode(context, tokenValue);
                } else {
                  context.extendedPseudoBracketsStack.push(tokenValue);
                  if (
                    context.extendedPseudoBracketsStack.length >
                    context.extendedPseudoNamesStack.length
                  ) {
                    updateBufferNode(context, tokenValue);
                  }
                }
              }
              if (isRegularSelectorNode(bufferNode)) {
                if (context.standardPseudoNamesStack.length > 0) {
                  updateBufferNode(context, tokenValue);
                  context.standardPseudoBracketsStack.push(tokenValue);
                }
                if (context.isAttributeBracketsOpen) {
                  updateBufferNode(context, tokenValue);
                }
              }
              if (isRelativePseudoClassNode(bufferNode)) {
                context.extendedPseudoBracketsStack.push(tokenValue);
              }
              break;
            case BRACKET.PARENTHESES.RIGHT:
              if (isAbsolutePseudoClassNode(bufferNode)) {
                if (
                  getNodeName(bufferNode) !== XPATH_PSEUDO_CLASS_MARKER &&
                  context.isRegexpOpen
                ) {
                  updateBufferNode(context, tokenValue);
                } else {
                  context.extendedPseudoBracketsStack.pop();
                  if (getNodeName(bufferNode) !== XPATH_PSEUDO_CLASS_MARKER) {
                    context.extendedPseudoNamesStack.pop();
                    if (
                      context.extendedPseudoBracketsStack.length >
                      context.extendedPseudoNamesStack.length
                    ) {
                      updateBufferNode(context, tokenValue);
                    } else if (
                      context.extendedPseudoBracketsStack.length >= 0 &&
                      context.extendedPseudoNamesStack.length >= 0
                    ) {
                      upToClosest(context, NODE.SELECTOR);
                    }
                  } else {
                    if (
                      context.extendedPseudoBracketsStack.length <
                      context.extendedPseudoNamesStack.length
                    ) {
                      context.extendedPseudoNamesStack.pop();
                    } else {
                      updateBufferNode(context, tokenValue);
                    }
                  }
                }
              }
              if (isRegularSelectorNode(bufferNode)) {
                if (context.isAttributeBracketsOpen) {
                  updateBufferNode(context, tokenValue);
                } else if (
                  context.standardPseudoNamesStack.length > 0 &&
                  context.standardPseudoBracketsStack.length > 0
                ) {
                  updateBufferNode(context, tokenValue);
                  context.standardPseudoBracketsStack.pop();
                  const lastStandardPseudo =
                    context.standardPseudoNamesStack.pop();
                  if (!lastStandardPseudo) {
                    throw new Error(
                      `Parsing error. Invalid selector: ${selector}`
                    );
                  }
                  if (
                    Object.values(REGULAR_PSEUDO_ELEMENTS).includes(
                      lastStandardPseudo
                    ) &&
                    nextTokenValue === COLON &&
                    nextToNextTokenValue &&
                    HAS_PSEUDO_CLASS_MARKERS.includes(nextToNextTokenValue)
                  ) {
                    throw new Error(
                      `Usage of :${nextToNextTokenValue}() pseudo-class is not allowed after any regular pseudo-element: '${lastStandardPseudo}'`
                    );
                  }
                } else {
                  context.extendedPseudoBracketsStack.pop();
                  context.extendedPseudoNamesStack.pop();
                  upToClosest(context, NODE.EXTENDED_SELECTOR);
                  upToClosest(context, NODE.SELECTOR);
                }
              }
              if (isSelectorNode(bufferNode)) {
                context.extendedPseudoBracketsStack.pop();
                context.extendedPseudoNamesStack.pop();
                upToClosest(context, NODE.EXTENDED_SELECTOR);
                upToClosest(context, NODE.SELECTOR);
              }
              if (isRelativePseudoClassNode(bufferNode)) {
                if (
                  context.extendedPseudoNamesStack.length > 0 &&
                  context.extendedPseudoBracketsStack.length > 0
                ) {
                  context.extendedPseudoBracketsStack.pop();
                  context.extendedPseudoNamesStack.pop();
                }
              }
              break;
            case LINE_FEED:
            case FORM_FEED:
            case CARRIAGE_RETURN:
              throw new Error(`'${selector}' is not a valid selector`);
            case TAB:
              if (
                isRegularSelectorNode(bufferNode) &&
                context.isAttributeBracketsOpen
              ) {
                updateBufferNode(context, tokenValue);
              } else {
                throw new Error(`'${selector}' is not a valid selector`);
              }
          }
          break;
        default:
          throw new Error(`Unknown type of token: '${tokenValue}'`);
      }
      i += 1;
    }
    if (context.ast === null) {
      throw new Error(`'${selector}' is not a valid selector`);
    }
    if (
      context.extendedPseudoNamesStack.length > 0 ||
      context.extendedPseudoBracketsStack.length > 0
    ) {
      throw new Error(
        `Unbalanced brackets for extended pseudo-class: '${getLast(
          context.extendedPseudoNamesStack
        )}'`
      );
    }
    if (context.isAttributeBracketsOpen) {
      throw new Error(
        `Unbalanced attribute brackets in selector: '${selector}'`
      );
    }
    return context.shouldOptimize ? optimizeAst(context.ast) : context.ast;
  };
  const natives = {
    MutationObserver: window.MutationObserver || window.WebKitMutationObserver,
  };
  class NativeTextContent {
    constructor() {
      this.nativeNode = window.Node || Node;
    }
    setGetter() {
      var _Object$getOwnPropert;
      this.getter =
        (_Object$getOwnPropert = Object.getOwnPropertyDescriptor(
          this.nativeNode.prototype,
          "textContent"
        )) === null || _Object$getOwnPropert === void 0
          ? void 0
          : _Object$getOwnPropert.get;
    }
  }
  const nativeTextContent = new NativeTextContent();
  const getNodeTextContent = (domElement) => {
    if (nativeTextContent.getter) {
      return nativeTextContent.getter.apply(domElement);
    }
    return domElement.textContent || "";
  };
  const getElementSelectorDesc = (element) => {
    let selectorText = element.tagName.toLowerCase();
    selectorText += Array.from(element.attributes)
      .map((attr) => {
        return `[${attr.name}="${element.getAttribute(attr.name)}"]`;
      })
      .join("");
    return selectorText;
  };
  const getElementSelectorPath = (inputEl) => {
    if (!(inputEl instanceof Element)) {
      throw new Error("Function received argument with wrong type");
    }
    let el;
    el = inputEl;
    const path = [];
    while (!!el && el.nodeType === Node.ELEMENT_NODE) {
      let selector = el.nodeName.toLowerCase();
      if (el.id && typeof el.id === "string") {
        selector += `#${el.id}`;
        path.unshift(selector);
        break;
      }
      let sibling = el;
      let nth = 1;
      while (sibling.previousElementSibling) {
        sibling = sibling.previousElementSibling;
        if (
          sibling.nodeType === Node.ELEMENT_NODE &&
          sibling.nodeName.toLowerCase() === selector
        ) {
          nth += 1;
        }
      }
      if (nth !== 1) {
        selector += `:nth-of-type(${nth})`;
      }
      path.unshift(selector);
      el = el.parentElement;
    }
    return path.join(" > ");
  };
  const isHtmlElement = (element) => {
    return element instanceof HTMLElement;
  };
  const getParent = (element, errorMessage) => {
    const { parentElement } = element;
    if (!parentElement) {
      throw new Error(errorMessage || "Element does no have parent element");
    }
    return parentElement;
  };
  const isErrorWithMessage = (error) => {
    return (
      typeof error === "object" &&
      error !== null &&
      "message" in error &&
      typeof error.message === "string"
    );
  };
  const toErrorWithMessage = (maybeError) => {
    if (isErrorWithMessage(maybeError)) {
      return maybeError;
    }
    try {
      return new Error(JSON.stringify(maybeError));
    } catch {
      return new Error(String(maybeError));
    }
  };
  const getErrorMessage = (error) => {
    return toErrorWithMessage(error).message;
  };
  const logger = {
    error:
      typeof console !== "undefined" && console.error && console.error.bind
        ? console.error.bind(window.console)
        : console.error,
    info:
      typeof console !== "undefined" && console.info && console.info.bind
        ? console.info.bind(window.console)
        : console.info,
  };
  const removeSuffix = (str, suffix) => {
    const index = str.indexOf(suffix, str.length - suffix.length);
    if (index >= 0) {
      return str.substring(0, index);
    }
    return str;
  };
  const replaceAll = (input, pattern, replacement) => {
    if (!input) {
      return input;
    }
    return input.split(pattern).join(replacement);
  };
  const toRegExp = (str) => {
    if (str.startsWith(SLASH) && str.endsWith(SLASH)) {
      return new RegExp(str.slice(1, -1));
    }
    const escaped = str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
    return new RegExp(escaped);
  };
  const convertTypeIntoString = (value) => {
    let output;
    switch (value) {
      case undefined:
        output = "undefined";
        break;
      case null:
        output = "null";
        break;
      default:
        output = value.toString();
    }
    return output;
  };
  const convertTypeFromString = (value) => {
    const numValue = Number(value);
    let output;
    if (!Number.isNaN(numValue)) {
      output = numValue;
    } else {
      switch (value) {
        case "undefined":
          output = undefined;
          break;
        case "null":
          output = null;
          break;
        case "true":
          output = true;
          break;
        case "false":
          output = false;
          break;
        default:
          output = value;
      }
    }
    return output;
  };
  const SAFARI_USER_AGENT_REGEXP = /\sVersion\/(\d{2}\.\d)(.+\s|\s)(Safari)\//;
  const isSafariBrowser = SAFARI_USER_AGENT_REGEXP.test(navigator.userAgent);
  const isUserAgentSupported = (userAgent) => {
    if (userAgent.includes("MSIE") || userAgent.includes("Trident/")) {
      return false;
    }
    return true;
  };
  const isBrowserSupported = () => {
    return isUserAgentSupported(navigator.userAgent);
  };
  const CSS_PROPERTY = {
    BACKGROUND: "background",
    BACKGROUND_IMAGE: "background-image",
    CONTENT: "content",
    OPACITY: "opacity",
  };
  const REGEXP_ANY_SYMBOL = ".*";
  const REGEXP_WITH_FLAGS_REGEXP = /^\s*\/.*\/[gmisuy]*\s*$/;
  const removeContentQuotes = (str) => {
    return str.replace(/^(["'])([\s\S]*)\1$/, "$2");
  };
  const addUrlPropertyQuotes = (str) => {
    if (!str.includes('url("')) {
      const re = /url\((.*?)\)/g;
      return str.replace(re, 'url("$1")');
    }
    return str;
  };
  const addUrlQuotesTo = {
    regexpArg: (str) => {
      const re = /(\^)?url(\\)?\\\((\w|\[\w)/g;
      return str.replace(re, '$1url$2\\(\\"?$3');
    },
    noneRegexpArg: addUrlPropertyQuotes,
  };
  const escapeRegExp = (str) => {
    const specials = [
      ".",
      "+",
      "?",
      "$",
      "{",
      "}",
      "(",
      ")",
      "[",
      "]",
      "\\",
      "/",
    ];
    const specialsRegex = new RegExp(`[${specials.join("\\")}]`, "g");
    return str.replace(specialsRegex, "\\$&");
  };
  const convertStyleMatchValueToRegexp = (rawValue) => {
    let value;
    if (rawValue.startsWith(SLASH) && rawValue.endsWith(SLASH)) {
      value = addUrlQuotesTo.regexpArg(rawValue);
      value = value.slice(1, -1);
    } else {
      value = addUrlQuotesTo.noneRegexpArg(rawValue);
      value = value.replace(/\\([\\()[\]"])/g, "$1");
      value = escapeRegExp(value);
      value = replaceAll(value, ASTERISK, REGEXP_ANY_SYMBOL);
    }
    return new RegExp(value, "i");
  };
  const normalizePropertyValue = (propertyName, propertyValue) => {
    let normalized = "";
    switch (propertyName) {
      case CSS_PROPERTY.BACKGROUND:
      case CSS_PROPERTY.BACKGROUND_IMAGE:
        normalized = addUrlPropertyQuotes(propertyValue);
        break;
      case CSS_PROPERTY.CONTENT:
        normalized = removeContentQuotes(propertyValue);
        break;
      case CSS_PROPERTY.OPACITY:
        normalized = isSafariBrowser
          ? (Math.round(parseFloat(propertyValue) * 100) / 100).toString()
          : propertyValue;
        break;
      default:
        normalized = propertyValue;
    }
    return normalized;
  };
  const getComputedStylePropertyValue = (
    domElement,
    propertyName,
    regularPseudoElement
  ) => {
    const style = window.getComputedStyle(domElement, regularPseudoElement);
    const propertyValue = style.getPropertyValue(propertyName);
    return normalizePropertyValue(propertyName, propertyValue);
  };
  const getPseudoArgData = (pseudoArg, separator) => {
    const index = pseudoArg.indexOf(separator);
    let name;
    let value;
    if (index > -1) {
      name = pseudoArg.substring(0, index).trim();
      value = pseudoArg.substring(index + 1).trim();
    } else {
      name = pseudoArg;
    }
    return {
      name,
      value,
    };
  };
  const parseStyleMatchArg = (pseudoName, rawArg) => {
    const { name, value } = getPseudoArgData(rawArg, COMMA);
    let regularPseudoElement = name;
    let styleMatchArg = value;
    if (!Object.values(REGULAR_PSEUDO_ELEMENTS).includes(name)) {
      regularPseudoElement = null;
      styleMatchArg = rawArg;
    }
    if (!styleMatchArg) {
      throw new Error(
        `Required style property argument part is missing in :${pseudoName}() arg: '${rawArg}'`
      );
    }
    if (regularPseudoElement) {
      regularPseudoElement = `${COLON}${COLON}${regularPseudoElement}`;
    }
    return {
      regularPseudoElement,
      styleMatchArg,
    };
  };
  const isStyleMatched = (argsData) => {
    const { pseudoName, pseudoArg, domElement } = argsData;
    const { regularPseudoElement, styleMatchArg } = parseStyleMatchArg(
      pseudoName,
      pseudoArg
    );
    const { name: matchName, value: matchValue } = getPseudoArgData(
      styleMatchArg,
      COLON
    );
    if (!matchName || !matchValue) {
      throw new Error(
        `Required property name or value is missing in :${pseudoName}() arg: '${styleMatchArg}'`
      );
    }
    let valueRegexp;
    try {
      valueRegexp = convertStyleMatchValueToRegexp(matchValue);
    } catch (e) {
      logger.error(getErrorMessage(e));
      throw new Error(
        `Invalid argument of :${pseudoName}() pseudo-class: '${styleMatchArg}'`
      );
    }
    const value = getComputedStylePropertyValue(
      domElement,
      matchName,
      regularPseudoElement
    );
    return valueRegexp && valueRegexp.test(value);
  };
  const validateStrMatcherArg = (arg) => {
    if (arg.includes(SLASH)) {
      return false;
    }
    if (!/^[\w-]+$/.test(arg)) {
      return false;
    }
    return true;
  };
  const getValidMatcherArg = function (rawArg) {
    let isWildcardAllowed =
      arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
    let arg;
    if (
      rawArg.length > 1 &&
      rawArg.startsWith(DOUBLE_QUOTE) &&
      rawArg.endsWith(DOUBLE_QUOTE)
    ) {
      rawArg = rawArg.slice(1, -1);
    }
    if (rawArg === "") {
      throw new Error("Argument should be specified. Empty arg is invalid.");
    }
    if (rawArg.startsWith(SLASH) && rawArg.endsWith(SLASH)) {
      if (rawArg.length > 2) {
        arg = toRegExp(rawArg);
      } else {
        throw new Error(`Invalid regexp: '${rawArg}'`);
      }
    } else if (rawArg.includes(ASTERISK)) {
      if (rawArg === ASTERISK && !isWildcardAllowed) {
        throw new Error(`Argument should be more specific than ${rawArg}`);
      }
      arg = replaceAll(rawArg, ASTERISK, REGEXP_ANY_SYMBOL);
      arg = new RegExp(arg);
    } else {
      if (!validateStrMatcherArg(rawArg)) {
        throw new Error(`Invalid argument: '${rawArg}'`);
      }
      arg = rawArg;
    }
    return arg;
  };
  const getRawMatchingData = (pseudoName, pseudoArg) => {
    const { name: rawName, value: rawValue } = getPseudoArgData(
      pseudoArg,
      EQUAL_SIGN
    );
    if (!rawName) {
      throw new Error(
        `Required attribute name is missing in :${pseudoName} arg: ${pseudoArg}`
      );
    }
    return {
      rawName,
      rawValue,
    };
  };
  const isAttributeMatched = (argsData) => {
    const { pseudoName, pseudoArg, domElement } = argsData;
    const elementAttributes = domElement.attributes;
    if (elementAttributes.length === 0) {
      return false;
    }
    const { rawName: rawAttrName, rawValue: rawAttrValue } = getRawMatchingData(
      pseudoName,
      pseudoArg
    );
    let attrNameMatch;
    try {
      attrNameMatch = getValidMatcherArg(rawAttrName);
    } catch (e) {
      const errorMessage = getErrorMessage(e);
      logger.error(errorMessage);
      throw new SyntaxError(errorMessage);
    }
    let isMatched = false;
    let i = 0;
    while (i < elementAttributes.length && !isMatched) {
      const attr = elementAttributes[i];
      if (!attr) {
        break;
      }
      const isNameMatched =
        attrNameMatch instanceof RegExp
          ? attrNameMatch.test(attr.name)
          : attrNameMatch === attr.name;
      if (!rawAttrValue) {
        isMatched = isNameMatched;
      } else {
        let attrValueMatch;
        try {
          attrValueMatch = getValidMatcherArg(rawAttrValue);
        } catch (e) {
          const errorMessage = getErrorMessage(e);
          logger.error(errorMessage);
          throw new SyntaxError(errorMessage);
        }
        const isValueMatched =
          attrValueMatch instanceof RegExp
            ? attrValueMatch.test(attr.value)
            : attrValueMatch === attr.value;
        isMatched = isNameMatched && isValueMatched;
      }
      i += 1;
    }
    return isMatched;
  };
  const parseRawPropChain = (input) => {
    if (
      input.length > 1 &&
      input.startsWith(DOUBLE_QUOTE) &&
      input.endsWith(DOUBLE_QUOTE)
    ) {
      input = input.slice(1, -1);
    }
    const chainChunks = input.split(DOT);
    const chainPatterns = [];
    let patternBuffer = "";
    let isRegexpPattern = false;
    let i = 0;
    while (i < chainChunks.length) {
      const chunk = getItemByIndex(
        chainChunks,
        i,
        `Invalid pseudo-class arg: '${input}'`
      );
      if (
        chunk.startsWith(SLASH) &&
        chunk.endsWith(SLASH) &&
        chunk.length > 2
      ) {
        chainPatterns.push(chunk);
      } else if (chunk.startsWith(SLASH)) {
        isRegexpPattern = true;
        patternBuffer += chunk;
      } else if (chunk.endsWith(SLASH)) {
        isRegexpPattern = false;
        patternBuffer += `.${chunk}`;
        chainPatterns.push(patternBuffer);
        patternBuffer = "";
      } else {
        if (isRegexpPattern) {
          patternBuffer += chunk;
        } else {
          chainPatterns.push(chunk);
        }
      }
      i += 1;
    }
    if (patternBuffer.length > 0) {
      throw new Error(`Invalid regexp property pattern '${input}'`);
    }
    const chainMatchPatterns = chainPatterns.map((pattern) => {
      if (pattern.length === 0) {
        throw new Error(
          `Empty pattern '${pattern}' is invalid in chain '${input}'`
        );
      }
      let validPattern;
      try {
        validPattern = getValidMatcherArg(pattern, true);
      } catch (e) {
        logger.error(getErrorMessage(e));
        throw new Error(
          `Invalid property pattern '${pattern}' in property chain '${input}'`
        );
      }
      return validPattern;
    });
    return chainMatchPatterns;
  };
  const filterRootsByRegexpChain = function (base, chain) {
    let output =
      arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
    const tempProp = getFirst(chain);
    if (chain.length === 1) {
      let key;
      for (key in base) {
        if (tempProp instanceof RegExp) {
          if (tempProp.test(key)) {
            output.push({
              base,
              prop: key,
              value: base[key],
            });
          }
        } else if (tempProp === key) {
          output.push({
            base,
            prop: tempProp,
            value: base[key],
          });
        }
      }
      return output;
    }
    if (tempProp instanceof RegExp) {
      const nextProp = chain.slice(1);
      const baseKeys = [];
      for (const key in base) {
        if (tempProp.test(key)) {
          baseKeys.push(key);
        }
      }
      baseKeys.forEach((key) => {
        var _Object$getOwnPropert;
        const item =
          (_Object$getOwnPropert = Object.getOwnPropertyDescriptor(
            base,
            key
          )) === null || _Object$getOwnPropert === void 0
            ? void 0
            : _Object$getOwnPropert.value;
        filterRootsByRegexpChain(item, nextProp, output);
      });
    }
    if (base && typeof tempProp === "string") {
      var _Object$getOwnPropert2;
      const nextBase =
        (_Object$getOwnPropert2 = Object.getOwnPropertyDescriptor(
          base,
          tempProp
        )) === null || _Object$getOwnPropert2 === void 0
          ? void 0
          : _Object$getOwnPropert2.value;
      chain = chain.slice(1);
      if (nextBase !== undefined) {
        filterRootsByRegexpChain(nextBase, chain, output);
      }
    }
    return output;
  };
  const isPropertyMatched = (argsData) => {
    const { pseudoName, pseudoArg, domElement } = argsData;
    const { rawName: rawPropertyName, rawValue: rawPropertyValue } =
      getRawMatchingData(pseudoName, pseudoArg);
    if (rawPropertyName.includes("\\/") || rawPropertyName.includes("\\.")) {
      throw new Error(
        `Invalid :${pseudoName} name pattern: ${rawPropertyName}`
      );
    }
    let propChainMatches;
    try {
      propChainMatches = parseRawPropChain(rawPropertyName);
    } catch (e) {
      const errorMessage = getErrorMessage(e);
      logger.error(errorMessage);
      throw new SyntaxError(errorMessage);
    }
    const ownerObjArr = filterRootsByRegexpChain(domElement, propChainMatches);
    if (ownerObjArr.length === 0) {
      return false;
    }
    let isMatched = true;
    if (rawPropertyValue) {
      let propValueMatch;
      try {
        propValueMatch = getValidMatcherArg(rawPropertyValue);
      } catch (e) {
        const errorMessage = getErrorMessage(e);
        logger.error(errorMessage);
        throw new SyntaxError(errorMessage);
      }
      if (propValueMatch) {
        for (let i = 0; i < ownerObjArr.length; i += 1) {
          var _ownerObjArr$i;
          const realValue =
            (_ownerObjArr$i = ownerObjArr[i]) === null ||
            _ownerObjArr$i === void 0
              ? void 0
              : _ownerObjArr$i.value;
          if (propValueMatch instanceof RegExp) {
            isMatched = propValueMatch.test(convertTypeIntoString(realValue));
          } else {
            if (realValue === "null" || realValue === "undefined") {
              isMatched = propValueMatch === realValue;
              break;
            }
            isMatched = convertTypeFromString(propValueMatch) === realValue;
          }
          if (isMatched) {
            break;
          }
        }
      }
    }
    return isMatched;
  };
  const isTextMatched = (argsData) => {
    const { pseudoName, pseudoArg, domElement } = argsData;
    const textContent = getNodeTextContent(domElement);
    let isTextContentMatched;
    let pseudoArgToMatch = pseudoArg;
    if (
      pseudoArgToMatch.startsWith(SLASH) &&
      REGEXP_WITH_FLAGS_REGEXP.test(pseudoArgToMatch)
    ) {
      const flagsIndex = pseudoArgToMatch.lastIndexOf("/");
      const flagsStr = pseudoArgToMatch.substring(flagsIndex + 1);
      pseudoArgToMatch = pseudoArgToMatch
        .substring(0, flagsIndex + 1)
        .slice(1, -1)
        .replace(/\\([\\"])/g, "$1");
      let regex;
      try {
        regex = new RegExp(pseudoArgToMatch, flagsStr);
      } catch (e) {
        throw new Error(
          `Invalid argument of :${pseudoName}() pseudo-class: ${pseudoArg}`
        );
      }
      isTextContentMatched = regex.test(textContent);
    } else {
      pseudoArgToMatch = pseudoArgToMatch.replace(/\\([\\()[\]"])/g, "$1");
      isTextContentMatched = textContent.includes(pseudoArgToMatch);
    }
    return isTextContentMatched;
  };
  const getValidNumberAncestorArg = (rawArg, pseudoName) => {
    const deep = Number(rawArg);
    if (Number.isNaN(deep) || deep < 1 || deep >= 256) {
      throw new Error(
        `Invalid argument of :${pseudoName} pseudo-class: '${rawArg}'`
      );
    }
    return deep;
  };
  const getNthAncestor = (domElement, nth, pseudoName) => {
    let ancestor = null;
    let i = 0;
    while (i < nth) {
      ancestor = domElement.parentElement;
      if (!ancestor) {
        throw new Error(
          `Out of DOM: Argument of :${pseudoName}() pseudo-class is too big — '${nth}'.`
        );
      }
      domElement = ancestor;
      i += 1;
    }
    return ancestor;
  };
  const validateStandardSelector = (selector) => {
    let isValid;
    try {
      document.querySelectorAll(selector);
      isValid = true;
    } catch (e) {
      isValid = false;
    }
    return isValid;
  };
  const matcherWrapper = (callback, argsData, errorMessage) => {
    let isMatched;
    try {
      isMatched = callback(argsData);
    } catch (e) {
      logger.error(getErrorMessage(e));
      throw new Error(errorMessage);
    }
    return isMatched;
  };
  const getAbsolutePseudoError = (propDesc, pseudoName, pseudoArg) => {
    return `${MATCHING_ELEMENT_ERROR_PREFIX} ${propDesc}, may be invalid :${pseudoName}() pseudo-class arg: '${pseudoArg}'`;
  };
  const isMatchedByAbsolutePseudo = (domElement, pseudoName, pseudoArg) => {
    let argsData;
    let errorMessage;
    let callback;
    switch (pseudoName) {
      case CONTAINS_PSEUDO:
      case HAS_TEXT_PSEUDO:
      case ABP_CONTAINS_PSEUDO:
        callback = isTextMatched;
        argsData = {
          pseudoName,
          pseudoArg,
          domElement,
        };
        errorMessage = getAbsolutePseudoError(
          "text content",
          pseudoName,
          pseudoArg
        );
        break;
      case MATCHES_CSS_PSEUDO:
      case MATCHES_CSS_AFTER_PSEUDO:
      case MATCHES_CSS_BEFORE_PSEUDO:
        callback = isStyleMatched;
        argsData = {
          pseudoName,
          pseudoArg,
          domElement,
        };
        errorMessage = getAbsolutePseudoError("style", pseudoName, pseudoArg);
        break;
      case MATCHES_ATTR_PSEUDO_CLASS_MARKER:
        callback = isAttributeMatched;
        argsData = {
          domElement,
          pseudoName,
          pseudoArg,
        };
        errorMessage = getAbsolutePseudoError(
          "attributes",
          pseudoName,
          pseudoArg
        );
        break;
      case MATCHES_PROPERTY_PSEUDO_CLASS_MARKER:
        callback = isPropertyMatched;
        argsData = {
          domElement,
          pseudoName,
          pseudoArg,
        };
        errorMessage = getAbsolutePseudoError(
          "properties",
          pseudoName,
          pseudoArg
        );
        break;
      default:
        throw new Error(`Unknown absolute pseudo-class :${pseudoName}()`);
    }
    return matcherWrapper(callback, argsData, errorMessage);
  };
  const findByAbsolutePseudoPseudo = {
    nthAncestor: (domElements, rawPseudoArg, pseudoName) => {
      const deep = getValidNumberAncestorArg(rawPseudoArg, pseudoName);
      const ancestors = domElements
        .map((domElement) => {
          let ancestor = null;
          try {
            ancestor = getNthAncestor(domElement, deep, pseudoName);
          } catch (e) {
            logger.error(getErrorMessage(e));
          }
          return ancestor;
        })
        .filter(isHtmlElement);
      return ancestors;
    },
    xpath: (domElements, rawPseudoArg) => {
      const foundElements = domElements.map((domElement) => {
        const result = [];
        let xpathResult;
        try {
          xpathResult = document.evaluate(
            rawPseudoArg,
            domElement,
            null,
            window.XPathResult.UNORDERED_NODE_ITERATOR_TYPE,
            null
          );
        } catch (e) {
          logger.error(getErrorMessage(e));
          throw new Error(
            `Invalid argument of :xpath() pseudo-class: '${rawPseudoArg}'`
          );
        }
        let node = xpathResult.iterateNext();
        while (node) {
          if (isHtmlElement(node)) {
            result.push(node);
          }
          node = xpathResult.iterateNext();
        }
        return result;
      });
      return flatten(foundElements);
    },
    upward: (domElements, rawPseudoArg) => {
      if (!validateStandardSelector(rawPseudoArg)) {
        throw new Error(
          `Invalid argument of :upward pseudo-class: '${rawPseudoArg}'`
        );
      }
      const closestAncestors = domElements
        .map((domElement) => {
          const parent = domElement.parentElement;
          if (!parent) {
            return null;
          }
          return parent.closest(rawPseudoArg);
        })
        .filter(isHtmlElement);
      return closestAncestors;
    },
  };
  const scopeDirectChildren = `${SCOPE_CSS_PSEUDO_CLASS}${CHILD_COMBINATOR}`;
  const scopeAnyChildren = `${SCOPE_CSS_PSEUDO_CLASS}${DESCENDANT_COMBINATOR}`;
  const getFirstInnerRegularChild = (selectorNode, pseudoName) => {
    return getFirstRegularChild(
      selectorNode.children,
      `RegularSelector is missing for :${pseudoName}() pseudo-class`
    );
  };
  const hasRelativesBySelectorList = (argsData) => {
    const { element, relativeSelectorList, pseudoName } = argsData;
    return relativeSelectorList.children.every((selectorNode) => {
      const relativeRegularSelector = getFirstInnerRegularChild(
        selectorNode,
        pseudoName
      );
      let specifiedSelector = "";
      let rootElement = null;
      const regularSelector = getNodeValue(relativeRegularSelector);
      if (
        regularSelector.startsWith(NEXT_SIBLING_COMBINATOR) ||
        regularSelector.startsWith(SUBSEQUENT_SIBLING_COMBINATOR)
      ) {
        rootElement = element.parentElement;
        const elementSelectorText = getElementSelectorDesc(element);
        specifiedSelector = `${scopeDirectChildren}${elementSelectorText}${regularSelector}`;
      } else if (regularSelector === ASTERISK) {
        rootElement = element;
        specifiedSelector = `${scopeAnyChildren}${ASTERISK}`;
      } else {
        specifiedSelector = `${scopeAnyChildren}${regularSelector}`;
        rootElement = element;
      }
      if (!rootElement) {
        throw new Error(
          `Selection by :${pseudoName}() pseudo-class is not possible`
        );
      }
      let relativeElements;
      try {
        relativeElements = getElementsForSelectorNode(
          selectorNode,
          rootElement,
          specifiedSelector
        );
      } catch (e) {
        logger.error(getErrorMessage(e));
        throw new Error(
          `Invalid selector for :${pseudoName}() pseudo-class: '${regularSelector}'`
        );
      }
      return relativeElements.length > 0;
    });
  };
  const isAnyElementBySelectorList = (argsData) => {
    const { element, relativeSelectorList, pseudoName } = argsData;
    return relativeSelectorList.children.some((selectorNode) => {
      const relativeRegularSelector = getFirstInnerRegularChild(
        selectorNode,
        pseudoName
      );
      const rootElement = getParent(
        element,
        `Selection by :${pseudoName}() pseudo-class is not possible`
      );
      const specifiedSelector = `${scopeDirectChildren}${getNodeValue(
        relativeRegularSelector
      )}`;
      let anyElements;
      try {
        anyElements = getElementsForSelectorNode(
          selectorNode,
          rootElement,
          specifiedSelector
        );
      } catch (e) {
        return false;
      }
      return anyElements.includes(element);
    });
  };
  const notElementBySelectorList = (argsData) => {
    const { element, relativeSelectorList, pseudoName } = argsData;
    return relativeSelectorList.children.every((selectorNode) => {
      const relativeRegularSelector = getFirstInnerRegularChild(
        selectorNode,
        pseudoName
      );
      const rootElement = getParent(
        element,
        `Selection by :${pseudoName}() pseudo-class is not possible`
      );
      const specifiedSelector = `${scopeDirectChildren}${getNodeValue(
        relativeRegularSelector
      )}`;
      let anyElements;
      try {
        anyElements = getElementsForSelectorNode(
          selectorNode,
          rootElement,
          specifiedSelector
        );
      } catch (e) {
        logger.error(getErrorMessage(e));
        throw new Error(
          `Invalid selector for :${pseudoName}() pseudo-class: '${getNodeValue(
            relativeRegularSelector
          )}'`
        );
      }
      return !anyElements.includes(element);
    });
  };
  const getByRegularSelector = (
    regularSelectorNode,
    root,
    specifiedSelector
  ) => {
    const selectorText = specifiedSelector
      ? specifiedSelector
      : getNodeValue(regularSelectorNode);
    let selectedElements = [];
    try {
      selectedElements = Array.from(root.querySelectorAll(selectorText));
    } catch (e) {
      throw new Error(
        `Error: unable to select by '${selectorText}' — ${getErrorMessage(e)}`
      );
    }
    return selectedElements;
  };
  const getByExtendedSelector = (domElements, extendedSelectorNode) => {
    let foundElements = [];
    const extendedPseudoClassNode = getPseudoClassNode(extendedSelectorNode);
    const pseudoName = getNodeName(extendedPseudoClassNode);
    if (isAbsolutePseudoClass(pseudoName)) {
      const absolutePseudoArg = getNodeValue(
        extendedPseudoClassNode,
        `Missing arg for :${pseudoName}() pseudo-class`
      );
      if (pseudoName === NTH_ANCESTOR_PSEUDO_CLASS_MARKER) {
        foundElements = findByAbsolutePseudoPseudo.nthAncestor(
          domElements,
          absolutePseudoArg,
          pseudoName
        );
      } else if (pseudoName === XPATH_PSEUDO_CLASS_MARKER) {
        try {
          document.createExpression(absolutePseudoArg, null);
        } catch (e) {
          throw new Error(
            `Invalid argument of :${pseudoName}() pseudo-class: '${absolutePseudoArg}'`
          );
        }
        foundElements = findByAbsolutePseudoPseudo.xpath(
          domElements,
          absolutePseudoArg
        );
      } else if (pseudoName === UPWARD_PSEUDO_CLASS_MARKER) {
        if (Number.isNaN(Number(absolutePseudoArg))) {
          foundElements = findByAbsolutePseudoPseudo.upward(
            domElements,
            absolutePseudoArg
          );
        } else {
          foundElements = findByAbsolutePseudoPseudo.nthAncestor(
            domElements,
            absolutePseudoArg,
            pseudoName
          );
        }
      } else {
        foundElements = domElements.filter((element) => {
          return isMatchedByAbsolutePseudo(
            element,
            pseudoName,
            absolutePseudoArg
          );
        });
      }
    } else if (isRelativePseudoClass(pseudoName)) {
      const relativeSelectorList = getRelativeSelectorListNode(
        extendedPseudoClassNode
      );
      let relativePredicate;
      switch (pseudoName) {
        case HAS_PSEUDO_CLASS_MARKER:
        case ABP_HAS_PSEUDO_CLASS_MARKER:
          relativePredicate = (element) =>
            hasRelativesBySelectorList({
              element,
              relativeSelectorList,
              pseudoName,
            });
          break;
        case IS_PSEUDO_CLASS_MARKER:
          relativePredicate = (element) =>
            isAnyElementBySelectorList({
              element,
              relativeSelectorList,
              pseudoName,
            });
          break;
        case NOT_PSEUDO_CLASS_MARKER:
          relativePredicate = (element) =>
            notElementBySelectorList({
              element,
              relativeSelectorList,
              pseudoName,
            });
          break;
        default:
          throw new Error(`Unknown relative pseudo-class: '${pseudoName}'`);
      }
      foundElements = domElements.filter(relativePredicate);
    } else {
      throw new Error(`Unknown extended pseudo-class: '${pseudoName}'`);
    }
    return foundElements;
  };
  const getByFollowingRegularSelector = (domElements, regularSelectorNode) => {
    let foundElements = [];
    const value = getNodeValue(regularSelectorNode);
    if (value.startsWith(CHILD_COMBINATOR)) {
      foundElements = domElements.map((root) => {
        const specifiedSelector = `${SCOPE_CSS_PSEUDO_CLASS}${value}`;
        return getByRegularSelector(
          regularSelectorNode,
          root,
          specifiedSelector
        );
      });
    } else if (
      value.startsWith(NEXT_SIBLING_COMBINATOR) ||
      value.startsWith(SUBSEQUENT_SIBLING_COMBINATOR)
    ) {
      foundElements = domElements.map((element) => {
        const rootElement = element.parentElement;
        if (!rootElement) {
          return [];
        }
        const elementSelectorText = getElementSelectorDesc(element);
        const specifiedSelector = `${scopeDirectChildren}${elementSelectorText}${value}`;
        const selected = getByRegularSelector(
          regularSelectorNode,
          rootElement,
          specifiedSelector
        );
        return selected;
      });
    } else {
      foundElements = domElements.map((root) => {
        const specifiedSelector = `${scopeAnyChildren}${getNodeValue(
          regularSelectorNode
        )}`;
        return getByRegularSelector(
          regularSelectorNode,
          root,
          specifiedSelector
        );
      });
    }
    return flatten(foundElements);
  };
  const getElementsForSelectorNode = (
    selectorNode,
    root,
    specifiedSelector
  ) => {
    let selectedElements = [];
    let i = 0;
    while (i < selectorNode.children.length) {
      const selectorNodeChild = getItemByIndex(
        selectorNode.children,
        i,
        "selectorNodeChild should be specified"
      );
      if (i === 0) {
        selectedElements = getByRegularSelector(
          selectorNodeChild,
          root,
          specifiedSelector
        );
      } else if (isExtendedSelectorNode(selectorNodeChild)) {
        selectedElements = getByExtendedSelector(
          selectedElements,
          selectorNodeChild
        );
      } else if (isRegularSelectorNode(selectorNodeChild)) {
        selectedElements = getByFollowingRegularSelector(
          selectedElements,
          selectorNodeChild
        );
      }
      i += 1;
    }
    return selectedElements;
  };
  const selectElementsByAst = function (ast) {
    let doc =
      arguments.length > 1 && arguments[1] !== undefined
        ? arguments[1]
        : document;
    const selectedElements = [];
    ast.children.forEach((selectorNode) => {
      selectedElements.push(...getElementsForSelectorNode(selectorNode, doc));
    });
    const uniqueElements = [...new Set(flatten(selectedElements))];
    return uniqueElements;
  };
  class ExtCssDocument {
    constructor() {
      this.astCache = new Map();
    }
    saveAstToCache(selector, ast) {
      this.astCache.set(selector, ast);
    }
    getAstFromCache(selector) {
      const cachedAst = this.astCache.get(selector) || null;
      return cachedAst;
    }
    getSelectorAst(selector) {
      let ast = this.getAstFromCache(selector);
      if (!ast) {
        ast = parse(selector);
      }
      this.saveAstToCache(selector, ast);
      return ast;
    }
    querySelectorAll(selector) {
      const ast = this.getSelectorAst(selector);
      return selectElementsByAst(ast);
    }
  }
  const extCssDocument = new ExtCssDocument();
  const getObjectFromEntries = (entries) => {
    const object = {};
    entries.forEach((el) => {
      const [key, value] = el;
      object[key] = value;
    });
    return object;
  };
  const DEBUG_PSEUDO_PROPERTY_KEY = "debug";
  const parseRemoveSelector = (rawSelector) => {
    const VALID_REMOVE_MARKER = `${COLON}${REMOVE_PSEUDO_MARKER}${BRACKET.PARENTHESES.LEFT}${BRACKET.PARENTHESES.RIGHT}`;
    const INVALID_REMOVE_MARKER = `${COLON}${REMOVE_PSEUDO_MARKER}${BRACKET.PARENTHESES.LEFT}`;
    let selector;
    let shouldRemove = false;
    const firstIndex = rawSelector.indexOf(VALID_REMOVE_MARKER);
    if (firstIndex === 0) {
      throw new Error(
        `${REMOVE_ERROR_PREFIX.NO_TARGET_SELECTOR}: '${rawSelector}'`
      );
    } else if (firstIndex > 0) {
      if (firstIndex !== rawSelector.lastIndexOf(VALID_REMOVE_MARKER)) {
        throw new Error(
          `${REMOVE_ERROR_PREFIX.MULTIPLE_USAGE}: '${rawSelector}'`
        );
      } else if (firstIndex + VALID_REMOVE_MARKER.length < rawSelector.length) {
        throw new Error(
          `${REMOVE_ERROR_PREFIX.INVALID_POSITION}: '${rawSelector}'`
        );
      } else {
        selector = rawSelector.substring(0, firstIndex);
        shouldRemove = true;
      }
    } else if (rawSelector.includes(INVALID_REMOVE_MARKER)) {
      throw new Error(
        `${REMOVE_ERROR_PREFIX.INVALID_REMOVE}: '${rawSelector}'`
      );
    } else {
      selector = rawSelector;
    }
    const stylesOfSelector = shouldRemove
      ? [
          {
            property: REMOVE_PSEUDO_MARKER,
            value: PSEUDO_PROPERTY_POSITIVE_VALUE,
          },
        ]
      : [];
    return {
      selector,
      stylesOfSelector,
    };
  };
  const parseSelectorRulePart = (selectorBuffer, extCssDoc) => {
    let selector = selectorBuffer.trim();
    if (selector.startsWith(AT_RULE_MARKER)) {
      throw new Error(`${NO_AT_RULE_ERROR_PREFIX}: '${selector}'.`);
    }
    let removeSelectorData;
    try {
      removeSelectorData = parseRemoveSelector(selector);
    } catch (e) {
      logger.error(getErrorMessage(e));
      throw new Error(`${REMOVE_ERROR_PREFIX.INVALID_REMOVE}: '${selector}'`);
    }
    let stylesOfSelector = [];
    let success = false;
    let ast;
    try {
      selector = removeSelectorData.selector;
      stylesOfSelector = removeSelectorData.stylesOfSelector;
      ast = extCssDoc.getSelectorAst(selector);
      success = true;
    } catch (e) {
      success = false;
    }
    return {
      success,
      selector,
      ast,
      stylesOfSelector,
    };
  };
  const createRawResultsMap = () => {
    return new Map();
  };
  const saveToRawResults = (rawResults, rawRuleData) => {
    const { selector, ast, rawStyles } = rawRuleData;
    if (!rawStyles) {
      throw new Error(`No style declaration for selector: '${selector}'`);
    }
    if (!ast) {
      throw new Error(`No ast parsed for selector: '${selector}'`);
    }
    const storedRuleData = rawResults.get(selector);
    if (!storedRuleData) {
      rawResults.set(selector, {
        ast,
        styles: rawStyles,
      });
    } else {
      storedRuleData.styles.push(...rawStyles);
    }
  };
  const isRemoveSetInStyles = (styles) => {
    return styles.some((s) => {
      return (
        s.property === REMOVE_PSEUDO_MARKER &&
        s.value === PSEUDO_PROPERTY_POSITIVE_VALUE
      );
    });
  };
  const getDebugStyleValue = (styles) => {
    const debugStyle = styles.find((s) => {
      return s.property === DEBUG_PSEUDO_PROPERTY_KEY;
    });
    return debugStyle === null || debugStyle === void 0
      ? void 0
      : debugStyle.value;
  };
  const prepareRuleData = (rawRuleData) => {
    const { selector, ast, rawStyles } = rawRuleData;
    if (!ast) {
      throw new Error(`AST should be parsed for selector: '${selector}'`);
    }
    if (!rawStyles) {
      throw new Error(`Styles should be parsed for selector: '${selector}'`);
    }
    const ruleData = {
      selector,
      ast,
    };
    const debugValue = getDebugStyleValue(rawStyles);
    const shouldRemove = isRemoveSetInStyles(rawStyles);
    let styles = rawStyles;
    if (debugValue) {
      styles = rawStyles.filter(
        (s) => s.property !== DEBUG_PSEUDO_PROPERTY_KEY
      );
      if (
        debugValue === PSEUDO_PROPERTY_POSITIVE_VALUE ||
        debugValue === DEBUG_PSEUDO_PROPERTY_GLOBAL_VALUE
      ) {
        ruleData.debug = debugValue;
      }
    }
    if (shouldRemove) {
      ruleData.style = {
        [REMOVE_PSEUDO_MARKER]: PSEUDO_PROPERTY_POSITIVE_VALUE,
      };
      const contentStyle = styles.find(
        (s) => s.property === CONTENT_CSS_PROPERTY
      );
      if (contentStyle) {
        ruleData.style[CONTENT_CSS_PROPERTY] = contentStyle.value;
      }
    } else {
      if (styles.length > 0) {
        const stylesAsEntries = styles.map((style) => {
          const { property, value } = style;
          return [property, value];
        });
        const preparedStyleData = getObjectFromEntries(stylesAsEntries);
        ruleData.style = preparedStyleData;
      }
    }
    return ruleData;
  };
  const combineRulesData = (rawResults) => {
    const results = [];
    rawResults.forEach((value, key) => {
      const selector = key;
      const { ast, styles: rawStyles } = value;
      results.push(
        prepareRuleData({
          selector,
          ast,
          rawStyles,
        })
      );
    });
    return results;
  };
  const tokenizeStyleBlock = (rawStyle) => {
    const styleDeclaration = rawStyle.trim();
    return tokenize(styleDeclaration, SUPPORTED_STYLE_DECLARATION_MARKS);
  };
  const DECLARATION_PART = {
    PROPERTY: "property",
    VALUE: "value",
  };
  const isValueQuotesOpen = (context) => {
    return context.bufferValue !== "" && context.valueQuoteMark !== null;
  };
  const collectStyle = (context) => {
    context.styles.push({
      property: context.bufferProperty.trim(),
      value: context.bufferValue.trim(),
    });
    context.bufferProperty = "";
    context.bufferValue = "";
  };
  const processPropertyToken = (context, styleBlock, token) => {
    const { value: tokenValue } = token;
    switch (token.type) {
      case TOKEN_TYPE.WORD:
        if (context.bufferProperty.length > 0) {
          throw new Error(
            `Invalid style property in style block: '${styleBlock}'`
          );
        }
        context.bufferProperty += tokenValue;
        break;
      case TOKEN_TYPE.MARK:
        if (tokenValue === COLON) {
          if (context.bufferProperty.trim().length === 0) {
            throw new Error(
              `Missing style property before ':' in style block: '${styleBlock}'`
            );
          }
          context.bufferProperty = context.bufferProperty.trim();
          context.processing = DECLARATION_PART.VALUE;
        } else if (WHITE_SPACE_CHARACTERS.includes(tokenValue));
        else {
          throw new Error(
            `Invalid style declaration in style block: '${styleBlock}'`
          );
        }
        break;
      default:
        throw new Error(
          `Unsupported style property character: '${tokenValue}' in style block: '${styleBlock}'`
        );
    }
  };
  const processValueToken = (context, styleBlock, token) => {
    const { value: tokenValue } = token;
    if (token.type === TOKEN_TYPE.WORD) {
      context.bufferValue += tokenValue;
    } else {
      switch (tokenValue) {
        case COLON:
          if (!isValueQuotesOpen(context)) {
            throw new Error(
              `Invalid style value for property '${context.bufferProperty}' in style block: '${styleBlock}'`
            );
          }
          context.bufferValue += tokenValue;
          break;
        case SEMICOLON:
          if (isValueQuotesOpen(context)) {
            context.bufferValue += tokenValue;
          } else {
            collectStyle(context);
            context.processing = DECLARATION_PART.PROPERTY;
          }
          break;
        case SINGLE_QUOTE:
        case DOUBLE_QUOTE:
          if (context.valueQuoteMark === null) {
            context.valueQuoteMark = tokenValue;
          } else if (
            !context.bufferValue.endsWith(BACKSLASH) &&
            context.valueQuoteMark === tokenValue
          ) {
            context.valueQuoteMark = null;
          }
          context.bufferValue += tokenValue;
          break;
        case BACKSLASH:
          if (!isValueQuotesOpen(context)) {
            throw new Error(
              `Invalid style value for property '${context.bufferProperty}' in style block: '${styleBlock}'`
            );
          }
          context.bufferValue += tokenValue;
          break;
        case SPACE:
        case TAB:
        case CARRIAGE_RETURN:
        case LINE_FEED:
        case FORM_FEED:
          if (context.bufferValue.length > 0) {
            context.bufferValue += tokenValue;
          }
          break;
        default:
          throw new Error(`Unknown style declaration token: '${tokenValue}'`);
      }
    }
  };
  const parseStyleBlock = (rawStyleBlock) => {
    const styleBlock = rawStyleBlock.trim();
    const tokens = tokenizeStyleBlock(styleBlock);
    const context = {
      processing: DECLARATION_PART.PROPERTY,
      styles: [],
      bufferProperty: "",
      bufferValue: "",
      valueQuoteMark: null,
    };
    let i = 0;
    while (i < tokens.length) {
      const token = tokens[i];
      if (!token) {
        break;
      }
      if (context.processing === DECLARATION_PART.PROPERTY) {
        processPropertyToken(context, styleBlock, token);
      } else if (context.processing === DECLARATION_PART.VALUE) {
        processValueToken(context, styleBlock, token);
      } else {
        throw new Error("Style declaration parsing failed");
      }
      i += 1;
    }
    if (isValueQuotesOpen(context)) {
      throw new Error(
        `Unbalanced style declaration quotes in style block: '${styleBlock}'`
      );
    }
    if (context.bufferProperty.length > 0) {
      if (context.bufferValue.length === 0) {
        throw new Error(
          `Missing style value for property '${context.bufferProperty}' in style block '${styleBlock}'`
        );
      }
      collectStyle(context);
    }
    if (context.styles.length === 0) {
      throw new Error(STYLE_ERROR_PREFIX.NO_STYLE);
    }
    return context.styles;
  };
  const getLeftCurlyBracketIndexes = (cssRule) => {
    const indexes = [];
    for (let i = 0; i < cssRule.length; i += 1) {
      if (cssRule[i] === BRACKET.CURLY.LEFT) {
        indexes.push(i);
      }
    }
    return indexes;
  };
  const parseRule = (rawCssRule, extCssDoc) => {
    var _rawRuleData$selector;
    const cssRule = rawCssRule.trim();
    if (
      cssRule.includes(`${SLASH}${ASTERISK}`) &&
      cssRule.includes(`${ASTERISK}${SLASH}`)
    ) {
      throw new Error(STYLE_ERROR_PREFIX.NO_COMMENT);
    }
    const leftCurlyBracketIndexes = getLeftCurlyBracketIndexes(cssRule);
    if (getFirst(leftCurlyBracketIndexes) === 0) {
      throw new Error(NO_SELECTOR_ERROR_PREFIX);
    }
    let selectorData;
    if (
      leftCurlyBracketIndexes.length > 0 &&
      !cssRule.includes(BRACKET.CURLY.RIGHT)
    ) {
      throw new Error(
        `${STYLE_ERROR_PREFIX.NO_STYLE} OR ${STYLE_ERROR_PREFIX.UNCLOSED_STYLE}`
      );
    }
    if (
      leftCurlyBracketIndexes.length === 0 ||
      !cssRule.includes(BRACKET.CURLY.RIGHT)
    ) {
      try {
        selectorData = parseSelectorRulePart(cssRule, extCssDoc);
        if (selectorData.success) {
          var _selectorData$stylesO;
          if (
            ((_selectorData$stylesO = selectorData.stylesOfSelector) === null ||
            _selectorData$stylesO === void 0
              ? void 0
              : _selectorData$stylesO.length) === 0
          ) {
            throw new Error(STYLE_ERROR_PREFIX.NO_STYLE_OR_REMOVE);
          }
          return {
            selector: selectorData.selector.trim(),
            ast: selectorData.ast,
            rawStyles: selectorData.stylesOfSelector,
          };
        } else {
          throw new Error("Invalid selector");
        }
      } catch (e) {
        throw new Error(getErrorMessage(e));
      }
    }
    let selectorBuffer;
    let styleBlockBuffer;
    const rawRuleData = {
      selector: "",
    };
    for (let i = leftCurlyBracketIndexes.length - 1; i > -1; i -= 1) {
      const index = leftCurlyBracketIndexes[i];
      if (!index) {
        throw new Error(
          `Impossible to continue, no '{' to process for rule: '${cssRule}'`
        );
      }
      selectorBuffer = cssRule.slice(0, index);
      styleBlockBuffer = cssRule.slice(index + 1, cssRule.length - 1);
      selectorData = parseSelectorRulePart(selectorBuffer, extCssDoc);
      if (selectorData.success) {
        var _rawRuleData$rawStyle;
        rawRuleData.selector = selectorData.selector.trim();
        rawRuleData.ast = selectorData.ast;
        rawRuleData.rawStyles = selectorData.stylesOfSelector;
        const parsedStyles = parseStyleBlock(styleBlockBuffer);
        (_rawRuleData$rawStyle = rawRuleData.rawStyles) === null ||
        _rawRuleData$rawStyle === void 0
          ? void 0
          : _rawRuleData$rawStyle.push(...parsedStyles);
        break;
      } else {
        continue;
      }
    }
    if (
      ((_rawRuleData$selector = rawRuleData.selector) === null ||
      _rawRuleData$selector === void 0
        ? void 0
        : _rawRuleData$selector.length) === 0
    ) {
      throw new Error("Selector in not valid");
    }
    return rawRuleData;
  };
  const parseRules$1 = (rawCssRules, extCssDoc) => {
    const rawResults = createRawResultsMap();
    const warnings = [];
    const uniqueRules = [...new Set(rawCssRules.map((r) => r.trim()))];
    uniqueRules.forEach((rule) => {
      try {
        saveToRawResults(rawResults, parseRule(rule, extCssDoc));
      } catch (e) {
        const errorMessage = getErrorMessage(e);
        warnings.push(`'${rule}' - error: '${errorMessage}'`);
      }
    });
    if (warnings.length > 0) {
      logger.info(`Invalid rules:\n  ${warnings.join("\n  ")}`);
    }
    return combineRulesData(rawResults);
  };
  const REGEXP_DECLARATION_END = /[;}]/g;
  const REGEXP_DECLARATION_DIVIDER = /[;:}]/g;
  const REGEXP_NON_WHITESPACE = /\S/g;
  const restoreRuleAcc = (context) => {
    context.rawRuleData = {
      selector: "",
    };
  };
  const parseSelectorPart = (context, extCssDoc) => {
    let selector = context.selectorBuffer.trim();
    if (selector.startsWith(AT_RULE_MARKER)) {
      throw new Error(`${NO_AT_RULE_ERROR_PREFIX}: '${selector}'.`);
    }
    let removeSelectorData;
    try {
      removeSelectorData = parseRemoveSelector(selector);
    } catch (e) {
      logger.error(getErrorMessage(e));
      throw new Error(`${REMOVE_ERROR_PREFIX.INVALID_REMOVE}: '${selector}'`);
    }
    if (context.nextIndex === -1) {
      if (selector === removeSelectorData.selector) {
        throw new Error(
          `${STYLE_ERROR_PREFIX.NO_STYLE_OR_REMOVE}: '${context.cssToParse}'`
        );
      }
      context.cssToParse = "";
    }
    let stylesOfSelector = [];
    let success = false;
    let ast;
    try {
      selector = removeSelectorData.selector;
      stylesOfSelector = removeSelectorData.stylesOfSelector;
      ast = extCssDoc.getSelectorAst(selector);
      success = true;
    } catch (e) {
      success = false;
    }
    if (context.nextIndex > 0) {
      context.cssToParse = context.cssToParse.slice(context.nextIndex);
    }
    return {
      success,
      selector,
      ast,
      stylesOfSelector,
    };
  };
  const parseUntilClosingBracket = (context, styles) => {
    REGEXP_DECLARATION_DIVIDER.lastIndex = context.nextIndex;
    let match = REGEXP_DECLARATION_DIVIDER.exec(context.cssToParse);
    if (match === null) {
      throw new Error(
        `${STYLE_ERROR_PREFIX.INVALID_STYLE}: '${context.cssToParse}'`
      );
    }
    let matchPos = match.index;
    let matched = match[0];
    if (matched === BRACKET.CURLY.RIGHT) {
      const declarationChunk = context.cssToParse.slice(
        context.nextIndex,
        matchPos
      );
      if (declarationChunk.trim().length === 0) {
        if (styles.length === 0) {
          throw new Error(
            `${STYLE_ERROR_PREFIX.NO_STYLE}: '${context.cssToParse}'`
          );
        }
      } else {
        throw new Error(
          `${STYLE_ERROR_PREFIX.INVALID_STYLE}: '${context.cssToParse}'`
        );
      }
      return matchPos;
    }
    if (matched === COLON) {
      const colonIndex = matchPos;
      REGEXP_DECLARATION_END.lastIndex = colonIndex;
      match = REGEXP_DECLARATION_END.exec(context.cssToParse);
      if (match === null) {
        throw new Error(
          `${STYLE_ERROR_PREFIX.UNCLOSED_STYLE}: '${context.cssToParse}'`
        );
      }
      matchPos = match.index;
      matched = match[0];
      const property = context.cssToParse
        .slice(context.nextIndex, colonIndex)
        .trim();
      if (property.length === 0) {
        throw new Error(
          `${STYLE_ERROR_PREFIX.NO_PROPERTY}: '${context.cssToParse}'`
        );
      }
      const value = context.cssToParse.slice(colonIndex + 1, matchPos).trim();
      if (value.length === 0) {
        throw new Error(
          `${STYLE_ERROR_PREFIX.NO_VALUE}: '${context.cssToParse}'`
        );
      }
      styles.push({
        property,
        value,
      });
      if (matched === BRACKET.CURLY.RIGHT) {
        return matchPos;
      }
    }
    context.cssToParse = context.cssToParse.slice(matchPos + 1);
    context.nextIndex = 0;
    return parseUntilClosingBracket(context, styles);
  };
  const parseNextStyle = (context) => {
    const styles = [];
    const styleEndPos = parseUntilClosingBracket(context, styles);
    REGEXP_NON_WHITESPACE.lastIndex = styleEndPos + 1;
    const match = REGEXP_NON_WHITESPACE.exec(context.cssToParse);
    if (match === null) {
      context.cssToParse = "";
      return styles;
    }
    const matchPos = match.index;
    context.cssToParse = context.cssToParse.slice(matchPos);
    return styles;
  };
  const parseStylesheet = (rawStylesheet, extCssDoc) => {
    const stylesheet = rawStylesheet.trim();
    if (
      stylesheet.includes(`${SLASH}${ASTERISK}`) &&
      stylesheet.includes(`${ASTERISK}${SLASH}`)
    ) {
      throw new Error(
        `${STYLE_ERROR_PREFIX.NO_COMMENT} in stylesheet: '${stylesheet}'`
      );
    }
    const context = {
      isSelector: true,
      nextIndex: 0,
      cssToParse: stylesheet,
      selectorBuffer: "",
      rawRuleData: {
        selector: "",
      },
    };
    const rawResults = createRawResultsMap();
    let selectorData;
    while (context.cssToParse) {
      if (context.isSelector) {
        context.nextIndex = context.cssToParse.indexOf(BRACKET.CURLY.LEFT);
        if (context.selectorBuffer.length === 0 && context.nextIndex === 0) {
          throw new Error(
            `${STYLE_ERROR_PREFIX.NO_SELECTOR}: '${context.cssToParse}'`
          );
        }
        if (context.nextIndex === -1) {
          context.selectorBuffer = context.cssToParse;
        } else {
          context.selectorBuffer += context.cssToParse.slice(
            0,
            context.nextIndex
          );
        }
        selectorData = parseSelectorPart(context, extCssDoc);
        if (selectorData.success) {
          context.rawRuleData.selector = selectorData.selector.trim();
          context.rawRuleData.ast = selectorData.ast;
          context.rawRuleData.rawStyles = selectorData.stylesOfSelector;
          context.isSelector = false;
          if (context.nextIndex === -1) {
            saveToRawResults(rawResults, context.rawRuleData);
            restoreRuleAcc(context);
          } else {
            context.nextIndex = 1;
            context.selectorBuffer = "";
          }
        } else {
          context.selectorBuffer += BRACKET.CURLY.LEFT;
          context.cssToParse = context.cssToParse.slice(1);
        }
      } else {
        var _context$rawRuleData$;
        const parsedStyles = parseNextStyle(context);
        (_context$rawRuleData$ = context.rawRuleData.rawStyles) === null ||
        _context$rawRuleData$ === void 0
          ? void 0
          : _context$rawRuleData$.push(...parsedStyles);
        saveToRawResults(rawResults, context.rawRuleData);
        context.nextIndex = 0;
        restoreRuleAcc(context);
        context.isSelector = true;
      }
    }
    return combineRulesData(rawResults);
  };
  const isNumber = (arg) => {
    return typeof arg === "number" && !Number.isNaN(arg);
  };
  class ThrottleWrapper {
    constructor(callback) {
      this.callback = callback;
      this.executeCallback = this.executeCallback.bind(this);
    }
    executeCallback() {
      this.lastRunTime = performance.now();
      if (isNumber(this.timerId)) {
        clearTimeout(this.timerId);
        delete this.timerId;
      }
      this.callback();
    }
    run() {
      if (isNumber(this.timerId)) {
        return;
      }
      if (isNumber(this.lastRunTime)) {
        const elapsedTime = performance.now() - this.lastRunTime;
        if (elapsedTime < ThrottleWrapper.THROTTLE_DELAY_MS) {
          this.timerId = window.setTimeout(
            this.executeCallback,
            ThrottleWrapper.THROTTLE_DELAY_MS - elapsedTime
          );
          return;
        }
      }
      this.timerId = window.setTimeout(this.executeCallback);
    }
  }
  _defineProperty(ThrottleWrapper, "THROTTLE_DELAY_MS", 150);
  const LAST_EVENT_TIMEOUT_MS = 10;
  const IGNORED_EVENTS = ["mouseover", "mouseleave", "mouseenter", "mouseout"];
  const SUPPORTED_EVENTS = [
    "keydown",
    "keypress",
    "keyup",
    "auxclick",
    "click",
    "contextmenu",
    "dblclick",
    "mousedown",
    "mouseenter",
    "mouseleave",
    "mousemove",
    "mouseover",
    "mouseout",
    "mouseup",
    "pointerlockchange",
    "pointerlockerror",
    "select",
    "wheel",
  ];
  const SAFARI_PROBLEMATIC_EVENTS = ["wheel"];
  class EventTracker {
    constructor() {
      _defineProperty(this, "getLastEventType", () => this.lastEventType);
      _defineProperty(this, "getTimeSinceLastEvent", () => {
        if (!this.lastEventTime) {
          return null;
        }
        return Date.now() - this.lastEventTime;
      });
      this.trackedEvents = isSafariBrowser
        ? SUPPORTED_EVENTS.filter(
            (event) => !SAFARI_PROBLEMATIC_EVENTS.includes(event)
          )
        : SUPPORTED_EVENTS;
      this.trackedEvents.forEach((eventName) => {
        document.documentElement.addEventListener(
          eventName,
          this.trackEvent,
          true
        );
      });
    }
    trackEvent(event) {
      this.lastEventType = event.type;
      this.lastEventTime = Date.now();
    }
    isIgnoredEventType() {
      const lastEventType = this.getLastEventType();
      const sinceLastEventTime = this.getTimeSinceLastEvent();
      return (
        !!lastEventType &&
        IGNORED_EVENTS.includes(lastEventType) &&
        !!sinceLastEventTime &&
        sinceLastEventTime < LAST_EVENT_TIMEOUT_MS
      );
    }
    stopTracking() {
      this.trackedEvents.forEach((eventName) => {
        document.documentElement.removeEventListener(
          eventName,
          this.trackEvent,
          true
        );
      });
    }
  }
  function shouldIgnoreMutations(mutations) {
    return !mutations.some((m) => m.type !== "attributes");
  }
  function observeDocument(context) {
    if (context.isDomObserved) {
      return;
    }
    context.isDomObserved = true;
    context.domMutationObserver = new natives.MutationObserver((mutations) => {
      if (!mutations || mutations.length === 0) {
        return;
      }
      const eventTracker = new EventTracker();
      if (
        eventTracker.isIgnoredEventType() &&
        shouldIgnoreMutations(mutations)
      ) {
        return;
      }
      context.eventTracker = eventTracker;
      context.scheduler.run();
    });
    context.domMutationObserver.observe(document, {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: ["id", "class"],
    });
  }
  function disconnectDocument(context) {
    if (!context.isDomObserved) {
      return;
    }
    context.isDomObserved = false;
    if (context.domMutationObserver) {
      context.domMutationObserver.disconnect();
    }
    if (context.eventTracker) {
      context.eventTracker.stopTracking();
    }
  }
  const CONTENT_ATTR_PREFIX_REGEXP = /^("|')adguard.+?/;
  const removeElement = (context, affectedElement) => {
    const { node } = affectedElement;
    affectedElement.removed = true;
    const elementSelector = getElementSelectorPath(node);
    const elementRemovalsCounter =
      context.removalsStatistic[elementSelector] || 0;
    if (elementRemovalsCounter > MAX_STYLE_PROTECTION_COUNT) {
      logger.error(
        `ExtendedCss: infinite loop protection for selector: '${elementSelector}'`
      );
      return;
    }
    if (node.parentElement) {
      node.parentElement.removeChild(node);
      context.removalsStatistic[elementSelector] = elementRemovalsCounter + 1;
    }
  };
  const setStyleToElement = (node, style) => {
    if (!(node instanceof HTMLElement)) {
      return;
    }
    Object.keys(style).forEach((prop) => {
      if (typeof node.style.getPropertyValue(prop.toString()) !== "undefined") {
        let value = style[prop];
        if (!value) {
          return;
        }
        if (
          prop === CONTENT_CSS_PROPERTY &&
          value.match(CONTENT_ATTR_PREFIX_REGEXP)
        ) {
          return;
        }
        value = removeSuffix(value.trim(), "!important").trim();
        node.style.setProperty(prop, value, "important");
      }
    });
  };
  const isIAffectedElement = (affectedElement) => {
    return (
      "node" in affectedElement &&
      "rules" in affectedElement &&
      affectedElement.rules instanceof Array
    );
  };
  const isAffectedElement = (affectedElement) => {
    return (
      "node" in affectedElement &&
      "originalStyle" in affectedElement &&
      "rules" in affectedElement &&
      affectedElement.rules instanceof Array
    );
  };
  const applyStyle = (context, rawAffectedElement) => {
    if (rawAffectedElement.protectionObserver) {
      return;
    }
    let affectedElement;
    if (context.beforeStyleApplied) {
      if (!isIAffectedElement(rawAffectedElement)) {
        throw new Error(
          "Returned IAffectedElement should have 'node' and 'rules' properties"
        );
      }
      affectedElement = context.beforeStyleApplied(rawAffectedElement);
      if (!affectedElement) {
        throw new Error(
          "Callback 'beforeStyleApplied' should return IAffectedElement"
        );
      }
    } else {
      affectedElement = rawAffectedElement;
    }
    if (!isAffectedElement(affectedElement)) {
      throw new Error(
        "Returned IAffectedElement should have 'node' and 'rules' properties"
      );
    }
    const { node, rules } = affectedElement;
    for (let i = 0; i < rules.length; i += 1) {
      const rule = rules[i];
      const selector =
        rule === null || rule === void 0 ? void 0 : rule.selector;
      const style = rule === null || rule === void 0 ? void 0 : rule.style;
      const debug = rule === null || rule === void 0 ? void 0 : rule.debug;
      if (style) {
        if (style[REMOVE_PSEUDO_MARKER] === PSEUDO_PROPERTY_POSITIVE_VALUE) {
          removeElement(context, affectedElement);
          return;
        }
        setStyleToElement(node, style);
      } else if (!debug) {
        throw new Error(
          `No style declaration in rule for selector: '${selector}'`
        );
      }
    }
  };
  const revertStyle = (affectedElement) => {
    if (affectedElement.protectionObserver) {
      affectedElement.protectionObserver.disconnect();
    }
    affectedElement.node.style.cssText = affectedElement.originalStyle;
  };
  class ExtMutationObserver {
    constructor(protectionCallback) {
      this.styleProtectionCount = 0;
      this.observer = new natives.MutationObserver((mutations) => {
        if (!mutations.length) {
          return;
        }
        this.styleProtectionCount += 1;
        protectionCallback(mutations, this);
      });
    }
    observe(target, options) {
      if (this.styleProtectionCount < MAX_STYLE_PROTECTION_COUNT) {
        this.observer.observe(target, options);
      } else {
        logger.error("ExtendedCss: infinite loop protection for style");
      }
    }
    disconnect() {
      this.observer.disconnect();
    }
  }
  const PROTECTION_OBSERVER_OPTIONS = {
    attributes: true,
    attributeOldValue: true,
    attributeFilter: ["style"],
  };
  const createProtectionCallback = (styles) => {
    const protectionCallback = (mutations, extObserver) => {
      if (!mutations[0]) {
        return;
      }
      const { target } = mutations[0];
      extObserver.disconnect();
      styles.forEach((style) => {
        setStyleToElement(target, style);
      });
      extObserver.observe(target, PROTECTION_OBSERVER_OPTIONS);
    };
    return protectionCallback;
  };
  const protectStyleAttribute = (node, rules) => {
    if (!natives.MutationObserver) {
      return null;
    }
    const styles = [];
    rules.forEach((ruleData) => {
      const { style } = ruleData;
      if (style) {
        styles.push(style);
      }
    });
    const protectionObserver = new ExtMutationObserver(
      createProtectionCallback(styles)
    );
    protectionObserver.observe(node, PROTECTION_OBSERVER_OPTIONS);
    return protectionObserver;
  };
  const STATS_DECIMAL_DIGITS_COUNT = 4;
  class TimingStats {
    constructor() {
      this.appliesTimings = [];
      this.appliesCount = 0;
      this.timingsSum = 0;
      this.meanTiming = 0;
      this.squaredSum = 0;
      this.standardDeviation = 0;
    }
    push(elapsedTimeMs) {
      this.appliesTimings.push(elapsedTimeMs);
      this.appliesCount += 1;
      this.timingsSum += elapsedTimeMs;
      this.meanTiming = this.timingsSum / this.appliesCount;
      this.squaredSum += elapsedTimeMs * elapsedTimeMs;
      this.standardDeviation = Math.sqrt(
        this.squaredSum / this.appliesCount - Math.pow(this.meanTiming, 2)
      );
    }
  }
  const beautifyTimingNumber = (timestamp) => {
    return Number(timestamp.toFixed(STATS_DECIMAL_DIGITS_COUNT));
  };
  const beautifyTimings = (rawTimings) => {
    return {
      appliesTimings: rawTimings.appliesTimings.map((t) =>
        beautifyTimingNumber(t)
      ),
      appliesCount: beautifyTimingNumber(rawTimings.appliesCount),
      timingsSum: beautifyTimingNumber(rawTimings.timingsSum),
      meanTiming: beautifyTimingNumber(rawTimings.meanTiming),
      standardDeviation: beautifyTimingNumber(rawTimings.standardDeviation),
    };
  };
  const printTimingInfo = (context) => {
    if (context.areTimingsPrinted) {
      return;
    }
    context.areTimingsPrinted = true;
    const timingsLogData = {};
    context.parsedRules.forEach((ruleData) => {
      if (ruleData.timingStats) {
        const { selector, style, debug, matchedElements } = ruleData;
        if (!style && !debug) {
          throw new Error(
            `Rule should have style declaration for selector: '${selector}'`
          );
        }
        const selectorData = {
          selectorParsed: selector,
          timings: beautifyTimings(ruleData.timingStats),
        };
        if (
          style &&
          style[REMOVE_PSEUDO_MARKER] === PSEUDO_PROPERTY_POSITIVE_VALUE
        ) {
          selectorData.removed = true;
        } else {
          selectorData.styleApplied = style || null;
          selectorData.matchedElements = matchedElements;
        }
        timingsLogData[selector] = selectorData;
      }
    });
    if (Object.keys(timingsLogData).length === 0) {
      return;
    }
    logger.info(
      "[ExtendedCss] Timings in milliseconds for %o:\n%o",
      window.location.href,
      timingsLogData
    );
  };
  const findAffectedElement = (affElements, domNode) => {
    return affElements.find((affEl) => affEl.node === domNode);
  };
  const applyRule = (context, ruleData) => {
    const isDebuggingMode = !!ruleData.debug || context.debug;
    let startTime;
    if (isDebuggingMode) {
      startTime = performance.now();
    }
    const { ast } = ruleData;
    const nodes = [];
    try {
      nodes.push(...selectElementsByAst(ast));
    } catch (e) {
      if (context.debug) {
        logger.error(getErrorMessage(e));
      }
    }
    nodes.forEach((node) => {
      let affectedElement = findAffectedElement(context.affectedElements, node);
      if (affectedElement) {
        affectedElement.rules.push(ruleData);
        applyStyle(context, affectedElement);
      } else {
        const originalStyle = node.style.cssText;
        affectedElement = {
          node,
          rules: [ruleData],
          originalStyle,
          protectionObserver: null,
        };
        applyStyle(context, affectedElement);
        context.affectedElements.push(affectedElement);
      }
    });
    if (isDebuggingMode && startTime) {
      const elapsedTimeMs = performance.now() - startTime;
      if (!ruleData.timingStats) {
        ruleData.timingStats = new TimingStats();
      }
      ruleData.timingStats.push(elapsedTimeMs);
    }
    return nodes;
  };
  const applyRules = (context) => {
    const newSelectedElements = [];
    disconnectDocument(context);
    context.parsedRules.forEach((ruleData) => {
      const nodes = applyRule(context, ruleData);
      Array.prototype.push.apply(newSelectedElements, nodes);
      if (ruleData.debug) {
        ruleData.matchedElements = nodes;
      }
    });
    let affLength = context.affectedElements.length;
    while (affLength) {
      const affectedElement = context.affectedElements[affLength - 1];
      if (!affectedElement) {
        break;
      }
      if (!newSelectedElements.includes(affectedElement.node)) {
        revertStyle(affectedElement);
        context.affectedElements.splice(affLength - 1, 1);
      } else if (!affectedElement.removed) {
        if (!affectedElement.protectionObserver) {
          affectedElement.protectionObserver = protectStyleAttribute(
            affectedElement.node,
            affectedElement.rules
          );
        }
      }
      affLength -= 1;
    }
    observeDocument(context);
    printTimingInfo(context);
  };
  class ExtendedCss {
    constructor(configuration) {
      if (!configuration) {
        throw new Error("ExtendedCss configuration should be provided.");
      }
      this.applyRulesCallbackListener =
        this.applyRulesCallbackListener.bind(this);
      this.context = {
        beforeStyleApplied: configuration.beforeStyleApplied,
        debug: false,
        affectedElements: [],
        isDomObserved: false,
        removalsStatistic: {},
        parsedRules: [],
        scheduler: new ThrottleWrapper(this.applyRulesCallbackListener),
      };
      if (!isBrowserSupported()) {
        logger.error("Browser is not supported by ExtendedCss");
        return;
      }
      if (!configuration.styleSheet && !configuration.cssRules) {
        throw new Error(
          "ExtendedCss configuration should have 'styleSheet' or 'cssRules' defined."
        );
      }
      if (configuration.styleSheet) {
        try {
          this.context.parsedRules.push(
            ...parseStylesheet(configuration.styleSheet, extCssDocument)
          );
        } catch (e) {
          throw new Error(
            `Pass the rules as configuration.cssRules since configuration.styleSheet cannot be parsed because of: '${getErrorMessage(
              e
            )}'`
          );
        }
      }
      if (configuration.cssRules) {
        this.context.parsedRules.push(
          ...parseRules$1(configuration.cssRules, extCssDocument)
        );
      }
      this.context.debug =
        configuration.debug ||
        this.context.parsedRules.some((ruleData) => {
          return ruleData.debug === DEBUG_PSEUDO_PROPERTY_GLOBAL_VALUE;
        });
      if (
        this.context.beforeStyleApplied &&
        typeof this.context.beforeStyleApplied !== "function"
      ) {
        throw new Error(
          `Invalid configuration. Type of 'beforeStyleApplied' should be a function, received: '${typeof this
            .context.beforeStyleApplied}'`
        );
      }
    }
    applyRulesCallbackListener() {
      applyRules(this.context);
    }
    init() {
      nativeTextContent.setGetter();
    }
    apply() {
      applyRules(this.context);
      if (document.readyState !== "complete") {
        document.addEventListener(
          "DOMContentLoaded",
          this.applyRulesCallbackListener,
          false
        );
      }
    }
    dispose() {
      disconnectDocument(this.context);
      this.context.affectedElements.forEach((el) => {
        revertStyle(el);
      });
      document.removeEventListener(
        "DOMContentLoaded",
        this.applyRulesCallbackListener,
        false
      );
    }
    getAffectedElements() {
      return this.context.affectedElements;
    }
    static query(selector) {
      let noTiming =
        arguments.length > 1 && arguments[1] !== undefined
          ? arguments[1]
          : true;
      if (typeof selector !== "string") {
        throw new Error("Selector should be defined as a string.");
      }
      const start = performance.now();
      try {
        return extCssDocument.querySelectorAll(selector);
      } finally {
        const end = performance.now();
        if (!noTiming) {
          logger.info(
            `[ExtendedCss] Elapsed: ${Math.round((end - start) * 1000)} μs.`
          );
        }
      }
    }
    static validate(inputSelector) {
      try {
        const { selector } = parseRemoveSelector(inputSelector);
        ExtendedCss.query(selector);
        return {
          ok: true,
          error: null,
        };
      } catch (e) {
        const error = `Error: Invalid selector: '${inputSelector}' -- ${getErrorMessage(
          e
        )}`;
        return {
          ok: false,
          error,
        };
      }
    }
  }

  function parseBRules() {
    data.appliedLevel = 0;
    values.brules.forEach((br) => {
      const level = bRuleParser(br);
      if (level > 0) data.bRules.push(br);
      if (level > data.appliedLevel) data.appliedLevel = level;
    });
  }
  function canApplyCss(type) {
    return (
      (data.appliedLevel & (type >= 2 ? 2 : 1)) == 0 &&
      data[styleBoxes[type]].length > 0
    );
  }

  function cleanRules() {
    if (
      confirm(`是否清空存储规则 ?

如果要卸载脚本,点击 确定 以后不要刷新,也不要打开任何新页面,
(如果可以)清空脚本存储(全选,删除,填 [],保存),然后删除脚本`)
    ) {
      values.rules = {};
      values.time = "0/0/0 0:0:0";
      values.etags = {};
      values.brules = [];
      values.hasSave.forEach((host) => {
        gmValue("set", true, `ajs_saved_styles_${host}`);
      });
      values.hasSave = [];
      data.isClean = true;
      gmMenu("update");
      gmMenu("export");
      gmMenu("count", () => location.reload());
    }
  }
  function reportRecord() {
    let text = "";
    function pushRecord(css) {
      const match = cssToAbp(css);
      if (match === null) return;
      const [item, type, sel] = match,
        count =
          type % 2 === 1
            ? ExtendedCss.query(sel).length
            : document.querySelectorAll(sel).length;
      if (count > 0) {
        text += `
! 匹配元素数量: ${count}
${item}
`;
      }
    }
    data.bRules.forEach((br) => {
      if (br.level > 0) {
        text += `
! 禁用${["", "通用", "特定", "所有"][br.level]}元素隐藏
${br.rule}
`;
      }
    });
    styleBoxes.forEach((box, i) => {
      if (canApplyCss(i)) {
        data[box]
          .split("\n")
          .filter((css, i, csss) => csss.indexOf(css) === i)
          .forEach((css) => pushRecord(css));
      }
    });
    if (text.length > 0) {
      downUrl(
        URL.createObjectURL(
          new Blob([
            `[Adblock Plus 2.0]
! 应用地址:
! ${location.href}
${text}`,
          ])
        ),
        `拦截报告_${location.hostname}.txt`
      );
    } else {
      alert("这个页面没有任何规则生效");
    }
  }
  function switchDisabledStat() {
    const disaList = values.black;
    data.disabled = !disaList.includes(location.hostname);
    if (data.disabled) {
      disaList.push(location.hostname);
    } else {
      disaList.splice(disaList.indexOf(location.hostname), 1);
    }
    values.black = disaList;
    location.reload();
  }

  function getCustomHash(saveHash) {
    return __awaiter(this, void 0, void 0, function* () {
      {
        if (location.protocol === "https:") {
          const hash = new Uint32Array(
            yield window.crypto.subtle.digest(
              "SHA-1",
              yield new Blob([data.customRules]).arrayBuffer()
            )
          ).toString();
          if (saveHash) values.hash = hash;
          return hash;
        } else {
          return values.hash;
        }
      }
    });
  }
  function initRules(apply) {
    let abpRules = {};
    data.receivedRules = "";
    abpRules = values.rules;
    {
      onlineRules.forEach((rule) => {
        const resRule = getRuleFromResource(rule.标识);
        if (resRule && !abpRules[rule.标识]) abpRules[rule.标识] = resRule;
      });
    }
    Object.keys(abpRules).forEach((name) => {
      data.receivedRules += "\n" + abpRules[name];
    });
    data.allRules = data.customRules + data.receivedRules;
    if (apply) splitRules();
    return data.receivedRules.length;
  }
  function styleApplyExec(type) {
    if (canApplyCss(type)) {
      const csss = data[styleBoxes[type]];
      new ExtendedCss({
        styleSheet: csss.replaceAll(/\/\*\s*\d.+?\s*\*\//g, ""),
      }).apply();
      if (!(type % 2 == 1)) addStyle(csss);
    }
  }
  function styleApply() {
    parseBRules();
    if (data.appliedLevel == 3) return;
    for (let type = 0; type < 4; type++) styleApplyExec(type);
    gmMenu("export", reportRecord);
  }
  function parseRules() {
    function addRule(rule, exten) {
      const [full, selector] = ruleToCss(rule, data.preset);
      const index = exten + (rule.generic ? 0 : 2);
      const checkResult = ExtendedCss.validate(selector);
      if (checkResult.ok) {
        data[styleBoxes[index]] += full;
        data.appliedCount++;
      } else {
        console.error("选择器检查错误:", rule, checkResult.error);
      }
    }
    styleBoxes.forEach((box) => {
      data[box] = "";
    });
    [data.styles, data.extStyles, data.selectors, data.extSelectors].forEach(
      (r, t) => {
        const sels = new Set();
        r.white.forEach((obj) => !sels.has(obj.sel) && sels.add(obj.sel));
        r.black
          .filter((obj) => !sels.has(obj.sel) && sels.add(obj.sel))
          .forEach((s) => addRule(s, t % 2));
      }
    );
    gmMenu("count", cleanRules);
    saveCss();
    if (!data.saved) styleApply();
  }
  function splitRules() {
    const bRules = [],
      sels = new Set();
    dataBoxes.forEach((box) => {
      data[box] = makeRuleBox();
    });
    data.allRules.split("\n").forEach((rule) => {
      if (isBasicRule(rule)) {
        const brule = bRuleSpliter(rule);
        if (brule) bRules.push(brule);
      } else {
        const ruleObj = ruleLoader(rule);
        if (typeof ruleObj !== "undefined") {
          if (ruleObj.black === "black" && sels.has(ruleObj.sel)) return;
          if (ruleObj.black === "white" && !sels.has(ruleObj.sel))
            sels.add(ruleObj.sel);
          data[dataBoxes[ruleObj.type]][ruleObj.black].push(ruleObj);
        }
      }
    });
    values.brules = bRules;
    parseRules();
  }

  function makeInitMenu() {
    gmMenu("count", cleanRules);
    {
      gmMenu("update", () =>
        __awaiter(this, void 0, void 0, function* () {
          yield performUpdate(true);
          location.reload();
        })
      );
    }
  }
  function storeRule(rule, resp) {
    let savedRules = {};
    savedRules = values.rules;
    if (resp.responseText) {
      let parsed = resp.responseText;
      if (rule.筛选后存储) {
        parsed = resp.responseText
          .split("\n")
          .filter((rule) => CRRE.test(rule) || isBasicRule(rule))
          .join("\n");
      }
      savedRules[rule.标识] = parsed;
      {
        values.rules = savedRules;
        if (values.rules[rule.标识].length !== 0) {
          const etag = extrEtag(resp),
            savedEtags = values.etags;
          if (etag) {
            savedEtags[rule.标识] = etag;
            values.etags = savedEtags;
          }
        }
      }
      data.receivedRules += "\n" + savedRules[rule.标识];
    }
  }
  function fetchRuleBody(rule) {
    var _a;
    return __awaiter(this, void 0, void 0, function* () {
      const getResp = yield promiseXhr({
        method: "GET",
        responseType: "text",
        url: rule.地址,
      }).catch((error) => {
        console.error("规则: ", rule.地址, " 下载错误: ", error);
      });
      if (
        (_a =
          getResp === null || getResp === void 0
            ? void 0
            : getResp.responseText) === null || _a === void 0
          ? void 0
          : _a.length
      ) {
        storeRule(rule, getResp);
        return true;
      } else return false;
    });
  }
  function fetchRuleGet(resp, rule) {
    const etag = extrEtag(resp),
      savedEtags = values.etags;
    return new Promise((resolve, reject) =>
      __awaiter(this, void 0, void 0, function* () {
        var _a;
        if (
          (_a =
            resp === null || resp === void 0 ? void 0 : resp.responseText) ===
            null || _a === void 0
            ? void 0
            : _a.length
        ) {
          storeRule(rule, resp);
          etag !==
          (savedEtags === null || savedEtags === void 0
            ? void 0
            : savedEtags[rule.标识])
            ? resolve()
            : reject("ETag 一致");
        } else {
          if (
            etag !==
            (savedEtags === null || savedEtags === void 0
              ? void 0
              : savedEtags[rule.标识])
          ) {
            (yield fetchRuleBody(rule)) ? resolve() : reject("GET 失败");
          } else reject("ETag 一致");
        }
      })
    );
  }
  function fetchRule(rule) {
    let headRespError;
    return new Promise((resolve, reject) =>
      __awaiter(this, void 0, void 0, function* () {
        var _a;
        const headResp = yield promiseXhr({
          method: "HEAD",
          responseType: "text",
          url: rule.地址,
        }).catch((error) => {
          headRespError = error;
          console.error("规则: ", rule.地址, " HEAD 错误: ", error);
        });
        if (!headResp) {
          // Via HEAD 会超时,但可以得到 ETag
          if (
            (_a =
              headRespError === null || headRespError === void 0
                ? void 0
                : headRespError.resp) === null || _a === void 0
              ? void 0
              : _a.responseHeaders
          ) {
            resolve(fetchRuleGet(headRespError.resp, rule));
          } else {
            reject("HEAD 失败");
          }
        } else {
          resolve(fetchRuleGet(headResp, rule));
        }
      })
    );
  }
  function fetchRules() {
    return __awaiter(this, void 0, void 0, function* () {
      let hasUpdate = onlineRules.length;
      data.updating = true;
      gmMenu("update", () => undefined);
      for (const rule of onlineRules) {
        if (rule.在线更新) {
          yield fetchRule(rule).catch((error) => {
            console.error("获取规则 ", rule, " 发生错误: ", error);
            hasUpdate--;
          });
        } else {
          hasUpdate--;
        }
      }
      values.time = new Date().toLocaleString("zh-CN");
      if (hasUpdate > 0) {
        values.hasSave.forEach((host) => {
          if (host === location.hostname) {
            initRules(true);
            data.updating = false;
            makeInitMenu();
          } else {
            const save = gmValue("get", true, `ajs_saved_styles_${host}`);
            if (save) {
              save.needUpdate = true;
              gmValue("set", true, `ajs_saved_styles_${host}`, save);
            }
          }
        });
      } else {
        data.updating = false;
        makeInitMenu();
      }
    });
  }
  function performUpdate(force) {
    if (data.isFrame) return Promise.reject();
    return force || new Date(values.time).getDate() !== new Date().getDate()
      ? fetchRules()
      : Promise.resolve();
  }

  function main() {
    return __awaiter(this, void 0, void 0, function* () {
      gmMenu("disable", switchDisabledStat);
      if (data.disabled) return;
      if (values.hasSave.includes(location.hostname)) readCss();
      const hash = yield getCustomHash(false);
      saved: {
        makeInitMenu();
        if (values.hash !== hash) {
          getCustomHash(true);
          initRules(true);
          break saved;
        }
        if (data.saved) {
          styleApply();
          if (!data.update) break saved;
        }
        if (initRules(false) === 0) {
          yield performUpdate(true);
          initRules(true);
        }
        splitRules();
      }
      {
        try {
          yield performUpdate(false);
        } catch (_error) {
          console.warn("iframe: ", location.href, " 取消更新");
        }
      }
    });
  }
  function runOnce(key, func) {
    if (key in cat.unsafeWindow) return Promise.reject();
    cat.unsafeWindow[key] = true;
    return func();
  }
  {
    runOnce(data.mutex, main);
  }
})({
  GM_info: typeof GM_info == "object" ? GM_info : {},
  unsafeWindow: typeof unsafeWindow == "object" ? unsafeWindow : window,
  GM_registerMenuCommand:
    typeof GM_registerMenuCommand == "function"
      ? GM_registerMenuCommand
      : undefined,
  GM_unregisterMenuCommand:
    typeof GM_unregisterMenuCommand == "function"
      ? GM_unregisterMenuCommand
      : undefined,
  GM_getValue: typeof GM_getValue == "function" ? GM_getValue : undefined,
  GM_deleteValue:
    typeof GM_deleteValue == "function" ? GM_deleteValue : undefined,
  GM_setValue: typeof GM_setValue == "function" ? GM_setValue : undefined,
  GM_addStyle: typeof GM_addStyle == "function" ? GM_addStyle : undefined,
  GM_xmlhttpRequest:
    typeof GM_xmlhttpRequest == "function" ? GM_xmlhttpRequest : undefined,
  GM_getResourceText:
    typeof GM_getResourceText == "function" ? GM_getResourceText : undefined,
});