extended-css

A javascript library that allows using extended CSS selectors (:has, :contains, etc)

目前為 2022-09-30 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.gf.qytechs.cn/scripts/452263/1099366/extended-css.js

// ==UserScript==
// @name extended-css
// @name:zh extended-css
// @name:zh-CN extended-css
// @name:zh_CN extended-css
// @version 1.3.16
// @namespace https://adguard.com/
// @author AdguardTeam
// @contributor AdguardTeam
// @contributors AdguardTeam
// @developer AdguardTeam
// @copyright GPL-3.0
// @license GPL-3.0
// @description A javascript library that allows using extended CSS selectors (:has, :contains, etc) 
// @description:zh 一个让用户可以使用扩展 CSS 选择器的库
// @description:zh-CN 一个让用户可以使用扩展 CSS 选择器的库
// @description:zh_CN 一个让用户可以使用扩展 CSS 选择器的库
// @homepage https://github.com/AdguardTeam/ExtendedCss
// @homepageURL https://github.com/AdguardTeam/ExtendedCss
// ==/UserScript==
/*! extended-css - v1.3.16 - Thu Sep 15 2022
* https://github.com/AdguardTeam/ExtendedCss
* Copyright (c) 2022 AdGuard. Licensed GPL-3.0
*/
var ExtendedCss = (function () {
  'use strict';

  function _typeof(obj) {
    "@babel/helpers - typeof";

    if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
      _typeof = function (obj) {
        return typeof obj;
      };
    } else {
      _typeof = function (obj) {
        return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
      };
    }

    return _typeof(obj);
  }

  function _slicedToArray(arr, i) {
    return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
  }

  function _toConsumableArray(arr) {
    return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
  }

  function _arrayWithoutHoles(arr) {
    if (Array.isArray(arr)) return _arrayLikeToArray(arr);
  }

  function _arrayWithHoles(arr) {
    if (Array.isArray(arr)) return arr;
  }

  function _iterableToArray(iter) {
    if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter);
  }

  function _iterableToArrayLimit(arr, i) {
    if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return;
    var _arr = [];
    var _n = true;
    var _d = false;
    var _e = undefined;

    try {
      for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
        _arr.push(_s.value);

        if (i && _arr.length === i) break;
      }
    } catch (err) {
      _d = true;
      _e = err;
    } finally {
      try {
        if (!_n && _i["return"] != null) _i["return"]();
      } finally {
        if (_d) throw _e;
      }
    }

    return _arr;
  }

  function _unsupportedIterableToArray(o, minLen) {
    if (!o) return;
    if (typeof o === "string") return _arrayLikeToArray(o, minLen);
    var n = Object.prototype.toString.call(o).slice(8, -1);
    if (n === "Object" && o.constructor) n = o.constructor.name;
    if (n === "Map" || n === "Set") return Array.from(o);
    if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
  }

  function _arrayLikeToArray(arr, len) {
    if (len == null || len > arr.length) len = arr.length;

    for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];

    return arr2;
  }

  function _nonIterableSpread() {
    throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
  }

  function _nonIterableRest() {
    throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
  }

  /**
   * Copyright 2016 Adguard Software Ltd
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */

  /* eslint-disable no-console */
  var utils = {};
  utils.MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
  /**
   * Stores native Node textContent getter to be used for contains pseudo-class
   * because elements' 'textContent' and 'innerText' properties might be mocked
   * https://github.com/AdguardTeam/ExtendedCss/issues/127
   */

  utils.nodeTextContentGetter = function () {
    var nativeNode = window.Node || Node;
    return Object.getOwnPropertyDescriptor(nativeNode.prototype, 'textContent').get;
  }();

  utils.isSafariBrowser = function () {
    return navigator.vendor === 'Apple Computer, Inc.';
  }();
  /**
   * Converts regular expressions passed as pseudo class arguments into RegExp instances.
   * Have to unescape doublequote " as well, because we escape them while enclosing such
   * arguments with doublequotes, and sizzle does not automatically unescapes them.
   */


  utils.pseudoArgToRegex = function (regexSrc, flag) {
    flag = flag || 'i';
    regexSrc = regexSrc.trim().replace(/\\(["\\])/g, '$1');
    return new RegExp(regexSrc, flag);
  };
  /**
   * Converts string to the regexp
   * @param {string} str
   * @returns {RegExp}
   */


  utils.toRegExp = function (str) {
    if (str[0] === '/' && str[str.length - 1] === '/') {
      return new RegExp(str.slice(1, -1));
    }

    var escaped = str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    return new RegExp(escaped);
  };

  utils.startsWith = function (str, prefix) {
    // if str === '', (str && false) will return ''
    // that's why it has to be !!str
    return !!str && str.indexOf(prefix) === 0;
  };

  utils.endsWith = function (str, postfix) {
    if (!str || !postfix) {
      return false;
    }

    if (str.endsWith) {
      return str.endsWith(postfix);
    }

    var t = String(postfix);
    var index = str.lastIndexOf(t);
    return index >= 0 && index === str.length - t.length;
  };
  /**
   * Helper function for creating regular expression from a url filter rule syntax.
   */


  utils.createURLRegex = function () {
    // Constants
    var regexConfiguration = {
      maskStartUrl: '||',
      maskPipe: '|',
      maskSeparator: '^',
      maskAnySymbol: '*',
      regexAnySymbol: '.*',
      regexSeparator: '([^ a-zA-Z0-9.%_-]|$)',
      regexStartUrl: '^(http|https|ws|wss)://([a-z0-9-_.]+\\.)?',
      regexStartString: '^',
      regexEndString: '$'
    }; // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/regexp
    // should be escaped . * + ? ^ $ { } ( ) | [ ] / \
    // except of * | ^

    var specials = ['.', '+', '?', '$', '{', '}', '(', ')', '[', ']', '\\', '/'];
    var specialsRegex = new RegExp("[".concat(specials.join('\\'), "]"), 'g');
    /**
     * Escapes regular expression string
     */

    var escapeRegExp = function escapeRegExp(str) {
      return str.replace(specialsRegex, '\\$&');
    };

    var replaceAll = function replaceAll(str, find, replace) {
      if (!str) {
        return str;
      }

      return str.split(find).join(replace);
    };
    /**
     * Main function that converts a url filter rule string to a regex.
     * @param {string} str
     * @return {RegExp}
     */


    var createRegexText = function createRegexText(str) {
      var regex = escapeRegExp(str);

      if (utils.startsWith(regex, regexConfiguration.maskStartUrl)) {
        regex = regex.substring(0, regexConfiguration.maskStartUrl.length) + replaceAll(regex.substring(regexConfiguration.maskStartUrl.length, regex.length - 1), '\|', '\\|') + regex.substring(regex.length - 1);
      } else if (utils.startsWith(regex, regexConfiguration.maskPipe)) {
        regex = regex.substring(0, regexConfiguration.maskPipe.length) + replaceAll(regex.substring(regexConfiguration.maskPipe.length, regex.length - 1), '\|', '\\|') + regex.substring(regex.length - 1);
      } else {
        regex = replaceAll(regex.substring(0, regex.length - 1), '\|', '\\|') + regex.substring(regex.length - 1);
      } // Replacing special url masks


      regex = replaceAll(regex, regexConfiguration.maskAnySymbol, regexConfiguration.regexAnySymbol);
      regex = replaceAll(regex, regexConfiguration.maskSeparator, regexConfiguration.regexSeparator);

      if (utils.startsWith(regex, regexConfiguration.maskStartUrl)) {
        regex = regexConfiguration.regexStartUrl + regex.substring(regexConfiguration.maskStartUrl.length);
      } else if (utils.startsWith(regex, regexConfiguration.maskPipe)) {
        regex = regexConfiguration.regexStartString + regex.substring(regexConfiguration.maskPipe.length);
      }

      if (utils.endsWith(regex, regexConfiguration.maskPipe)) {
        regex = regex.substring(0, regex.length - 1) + regexConfiguration.regexEndString;
      }

      return new RegExp(regex, 'i');
    };

    return createRegexText;
  }();
  /**
   * Creates an object implementing Location interface from a url.
   * An alternative to URL.
   * https://github.com/AdguardTeam/FingerprintingBlocker/blob/master/src/shared/url.ts#L64
   */


  utils.createLocation = function (href) {
    var anchor = document.createElement('a');
    anchor.href = href;

    if (anchor.host === '') {
      anchor.href = anchor.href; // eslint-disable-line no-self-assign
    }

    return anchor;
  };
  /**
   * Checks whether A has the same origin as B.
   * @param {string} urlA location.href of A.
   * @param {Location} locationB location of B.
   * @param {string} domainB document.domain of B.
   * @return {boolean}
   */


  utils.isSameOrigin = function (urlA, locationB, domainB) {
    var locationA = utils.createLocation(urlA); // eslint-disable-next-line no-script-url

    if (locationA.protocol === 'javascript:' || locationA.href === 'about:blank') {
      return true;
    }

    if (locationA.protocol === 'data:' || locationA.protocol === 'file:') {
      return false;
    }

    return locationA.hostname === domainB && locationA.port === locationB.port && locationA.protocol === locationB.protocol;
  };
  /**
   * A helper class to throttle function calls with setTimeout and requestAnimationFrame.
   */


  utils.AsyncWrapper = function () {
    var supported = typeof window.requestAnimationFrame !== 'undefined';
    var rAF = supported ? requestAnimationFrame : setTimeout;
    var cAF = supported ? cancelAnimationFrame : clearTimeout;
    var perf = supported ? performance : Date;
    /**
     * @param {Function} callback
     * @param {number} throttle number, the provided callback should be executed twice
     * in this time frame.
     * @constructor
     */

    function AsyncWrapper(callback, throttle) {
      this.callback = callback;
      this.throttle = throttle;
      this.wrappedCallback = this.wrappedCallback.bind(this);

      if (this.wrappedAsapCallback) {
        this.wrappedAsapCallback = this.wrappedAsapCallback.bind(this);
      }
    }
    /** @private */


    AsyncWrapper.prototype.wrappedCallback = function (ts) {
      this.lastRun = isNumber(ts) ? ts : perf.now();
      delete this.rAFid;
      delete this.timerId;
      delete this.asapScheduled;
      this.callback();
    };
    /** @private Indicates whether there is a scheduled callback. */


    AsyncWrapper.prototype.hasPendingCallback = function () {
      return isNumber(this.rAFid) || isNumber(this.timerId);
    };
    /**
     * Schedules a function call before the next animation frame.
     */


    AsyncWrapper.prototype.run = function () {
      if (this.hasPendingCallback()) {
        // There is a pending execution scheduled.
        return;
      }

      if (typeof this.lastRun !== 'undefined') {
        var elapsed = perf.now() - this.lastRun;

        if (elapsed < this.throttle) {
          this.timerId = setTimeout(this.wrappedCallback, this.throttle - elapsed);
          return;
        }
      }

      this.rAFid = rAF(this.wrappedCallback);
    };
    /**
     * Schedules a function call in the most immenent microtask.
     * This cannot be canceled.
     */


    AsyncWrapper.prototype.runAsap = function () {
      if (this.asapScheduled) {
        return;
      }

      this.asapScheduled = true;
      cAF(this.rAFid);
      clearTimeout(this.timerId);

      if (utils.MutationObserver) {
        /**
         * Using MutationObservers to access microtask queue is a standard technique,
         * used in ASAP library
         * {@link https://github.com/kriskowal/asap/blob/master/browser-raw.js#L140}
         */
        if (!this.mo) {
          this.mo = new utils.MutationObserver(this.wrappedCallback);
          this.node = document.createTextNode(1);
          this.mo.observe(this.node, {
            characterData: true
          });
        }

        this.node.nodeValue = -this.node.nodeValue;
      } else {
        setTimeout(this.wrappedCallback);
      }
    };
    /**
     * Runs scheduled execution immediately, if there were any.
     */


    AsyncWrapper.prototype.runImmediately = function () {
      if (this.hasPendingCallback()) {
        cAF(this.rAFid);
        clearTimeout(this.timerId);
        delete this.rAFid;
        delete this.timerId;
        this.wrappedCallback();
      }
    };

    AsyncWrapper.now = function () {
      return perf.now();
    };

    return AsyncWrapper;
  }();
  /**
   * Stores native OdP to be used in WeakMap and Set polyfills.
   */


  utils.defineProperty = Object.defineProperty;
  utils.WeakMap = typeof WeakMap !== 'undefined' ? WeakMap : function () {
    /** Originally based on {@link https://github.com/Polymer/WeakMap} */
    var counter = Date.now() % 1e9;

    var WeakMap = function WeakMap() {
      this.name = "__st".concat(Math.random() * 1e9 >>> 0).concat(counter++, "__");
    };

    WeakMap.prototype = {
      set: function set(key, value) {
        var entry = key[this.name];

        if (entry && entry[0] === key) {
          entry[1] = value;
        } else {
          utils.defineProperty(key, this.name, {
            value: [key, value],
            writable: true
          });
        }

        return this;
      },
      get: function get(key) {
        var entry = key[this.name];
        return entry && entry[0] === key ? entry[1] : undefined;
      },
      delete: function _delete(key) {
        var entry = key[this.name];

        if (!entry) {
          return false;
        }

        var hasValue = entry[0] === key;
        delete entry[0];
        delete entry[1];
        return hasValue;
      },
      has: function has(key) {
        var entry = key[this.name];

        if (!entry) {
          return false;
        }

        return entry[0] === key;
      }
    };
    return WeakMap;
  }();
  utils.Set = typeof Set !== 'undefined' ? Set : function () {
    var counter = Date.now() % 1e9;
    /**
     * A polyfill which covers only the basic usage.
     * Only supports methods that are supported in IE11.
     * {@link https://docs.microsoft.com/en-us/scripting/javascript/reference/set-object-javascript}
     * Assumes that 'key's are all objects, not primitives such as a number.
     *
     * @param {Array} items Initial items in this set
     */

    var Set = function Set(items) {
      this.name = "__st".concat(Math.random() * 1e9 >>> 0).concat(counter++, "__");
      this.keys = [];

      if (items && items.length) {
        var iItems = items.length;

        while (iItems--) {
          this.add(items[iItems]);
        }
      }
    };

    Set.prototype = {
      add: function add(key) {
        if (!isNumber(key[this.name])) {
          var index = this.keys.push(key) - 1;
          utils.defineProperty(key, this.name, {
            value: index,
            writable: true
          });
        }
      },
      delete: function _delete(key) {
        if (isNumber(key[this.name])) {
          var index = key[this.name];
          delete this.keys[index];
          key[this.name] = undefined;
        }
      },
      has: function has(key) {
        return isNumber(key[this.name]);
      },
      clear: function clear() {
        this.keys.forEach(function (key) {
          key[this.name] = undefined;
        });
        this.keys.length = 0;
      },
      forEach: function forEach(cb) {
        var that = this;
        this.keys.forEach(function (value) {
          cb(value, value, that);
        });
      }
    };
    utils.defineProperty(Set.prototype, 'size', {
      get: function get() {
        // Skips holes.
        return this.keys.reduce(function (acc) {
          return acc + 1;
        }, 0);
      }
    });
    return Set;
  }();
  /**
   * Vendor-specific Element.prototype.matches
   */

  utils.matchesPropertyName = function () {
    var props = ['matches', 'matchesSelector', 'mozMatchesSelector', 'msMatchesSelector', 'oMatchesSelector', 'webkitMatchesSelector'];

    for (var i = 0; i < 6; i++) {
      if (Element.prototype.hasOwnProperty(props[i])) {
        return props[i];
      }
    }
  }();
  /**
   * Provides stats information
   */


  utils.Stats = function () {
    /** @member {Array<number>} */
    this.array = [];
    /** @member {number} */

    this.length = 0;
    var zeroDescriptor = {
      value: 0,
      writable: true
    };
    /** @member {number} @private */

    Object.defineProperty(this, 'sum', zeroDescriptor);
    /** @member {number} @private */

    Object.defineProperty(this, 'squaredSum', zeroDescriptor);
  };
  /**
   * @param {number} dataPoint data point
   */


  utils.Stats.prototype.push = function (dataPoint) {
    this.array.push(dataPoint);
    this.length++;
    this.sum += dataPoint;
    this.squaredSum += dataPoint * dataPoint;
    /** @member {number} */

    this.mean = this.sum / this.length;
    /** @member {number} */
    // eslint-disable-next-line no-restricted-properties

    this.stddev = Math.sqrt(this.squaredSum / this.length - Math.pow(this.mean, 2));
  };
  /** Safe console.error version */


  utils.logError = typeof console !== 'undefined' && console.error && Function.prototype.bind && console.error.bind ? console.error.bind(window.console) : console.error;
  /** Safe console.info version */

  utils.logInfo = typeof console !== 'undefined' && console.info && Function.prototype.bind && console.info.bind ? console.info.bind(window.console) : console.info;

  function isNumber(obj) {
    return typeof obj === 'number';
  }
  /**
   * Returns path to element we will use as element identifier
   * @param {Element} inputEl
   * @returns {string} - path to the element
   */


  utils.getNodeSelector = function (inputEl) {
    if (!(inputEl instanceof Element)) {
      throw new Error('Function received argument with wrong type');
    }

    var el = inputEl;
    var path = []; // we need to check '!!el' first because it is possible
    // that some ancestor of the inputEl was removed before it

    while (!!el && el.nodeType === Node.ELEMENT_NODE) {
      var selector = el.nodeName.toLowerCase();

      if (el.id && typeof el.id === 'string') {
        selector += "#".concat(el.id);
        path.unshift(selector);
        break;
      } else {
        var sibling = el;
        var nth = 1;

        while (sibling.previousSibling) {
          sibling = sibling.previousSibling;

          if (sibling.nodeType === Node.ELEMENT_NODE && sibling.nodeName.toLowerCase() === selector) {
            nth++;
          }
        }

        if (nth !== 1) {
          selector += ":nth-of-type(".concat(nth, ")");
        }
      }

      path.unshift(selector);
      el = el.parentNode;
    }

    return path.join(' > ');
  };

  /**
   * Copyright 2016 Adguard Software Ltd
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */

  /**
   * Helper class css utils
   *
   * @type {{normalize}}
   */
  var cssUtils = function () {
    /**
     * Regex that matches AdGuard's backward compatible syntaxes.
     */
    var reAttrFallback = /\[-(?:ext|abp)-([a-z-_]+)=(["'])((?:(?=(\\?))\4.)*?)\2\]/g;
    /**
     * Complex replacement function.
     * Unescapes quote characters inside of an extended selector.
     *
     * @param match     Whole matched string
     * @param name      Group 1
     * @param quoteChar Group 2
     * @param value     Group 3
     */

    var evaluateMatch = function evaluateMatch(match, name, quoteChar, value) {
      // Unescape quotes
      var re = new RegExp("([^\\\\]|^)\\\\".concat(quoteChar), 'g');
      value = value.replace(re, "$1".concat(quoteChar));
      return ":".concat(name, "(").concat(value, ")");
    }; // Sizzle's parsing of pseudo class arguments is buggy on certain circumstances
    // We support following form of arguments:
    // 1. for :matches-css, those of a form {propertyName}: /.*/
    // 2. for :contains, those of a form /.*/
    // We transform such cases in a way that Sizzle has no ambiguity in parsing arguments.


    var reMatchesCss = /\:(matches-css(?:-after|-before)?)\(([a-z-\s]*\:\s*\/(?:\\.|[^\/])*?\/\s*)\)/g;
    var reContains = /:(?:-abp-)?(contains|has-text)\((\s*\/(?:\\.|[^\/])*?\/\s*)\)/g;
    var reScope = /\(\:scope >/g; // Note that we require `/` character in regular expressions to be escaped.

    /**
     * Used for pre-processing pseudo-classes values with above two regexes.
     */

    var addQuotes = function addQuotes(_, c1, c2) {
      return ":".concat(c1, "(\"").concat(c2.replace(/["\\]/g, '\\$&'), "\")");
    };

    var SCOPE_REPLACER = '(>';
    /**
     * Normalizes specified css text in a form that can be parsed by the
     * Sizzle engine.
     * Normalization means
     *  1. transforming [-ext-*=""] attributes to pseudo classes
     *  2. enclosing possibly ambiguous arguments of `:contains`,
     *     `:matches-css` pseudo classes with quotes.
     * @param {string} cssText
     * @return {string}
     */

    var normalize = function normalize(cssText) {
      var normalizedCssText = cssText.replace(reAttrFallback, evaluateMatch).replace(reMatchesCss, addQuotes).replace(reContains, addQuotes).replace(reScope, SCOPE_REPLACER);
      return normalizedCssText;
    };

    var isSimpleSelectorValid = function isSimpleSelectorValid(selector) {
      try {
        document.querySelectorAll(selector);
      } catch (e) {
        return false;
      }

      return true;
    };

    return {
      normalize: normalize,
      isSimpleSelectorValid: isSimpleSelectorValid
    };
  }();

  /*!
   * Sizzle CSS Selector Engine v2.3.4-pre-adguard
   * https://sizzlejs.com/
   *
   * Copyright JS Foundation and other contributors
   * Released under the MIT license
   * https://js.foundation/
   *
   * Date: 2020-08-04
   */

  /**
   * Version of Sizzle patched by AdGuard in order to be used in the ExtendedCss module.
   * https://github.com/AdguardTeam/sizzle-extcss
   *
   * Look for [AdGuard Patch] and ADGUARD_EXTCSS markers to find out what exactly was changed by us.
   *
   * Global changes:
   * 1. Added additional parameters to the "Sizzle.tokenize" method so that it can be used for stylesheets parsing and validation.
   * 2. Added tokens re-sorting mechanism forcing slow pseudos to be matched last  (see sortTokenGroups).
   * 3. Fix the nonnativeSelectorCache caching -- there was no value corresponding to a key.
   * 4. Added Sizzle.compile call to the `:has` pseudo definition.
   *
   * Changes that are applied to the ADGUARD_EXTCSS build only:
   * 1. Do not expose Sizzle to the global scope. Initialize it lazily via initializeSizzle().
   * 2. Removed :contains pseudo declaration -- its syntax is changed and declared outside of Sizzle.
   * 3. Removed declarations for the following non-standard pseudo classes:
   * :parent, :header, :input, :button, :text, :first, :last, :eq,
   * :even, :odd, :lt, :gt, :nth, :radio, :checkbox, :file,
   * :password, :image, :submit, :reset
   * 4. Added es6 module export
   */
  var Sizzle;
  /**
   * Initializes Sizzle object.
   * In the case of AdGuard ExtendedCss we want to avoid initializing Sizzle right away
   * and exposing it to the global scope.
   */

  var initializeSizzle = function initializeSizzle() {
    // jshint ignore:line
    if (!Sizzle) {
      //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
      Sizzle = function (window) {
        var support,
            Expr,
            getText,
            isXML,
            tokenize,
            compile,
            select,
            outermostContext,
            sortInput,
            hasDuplicate,
            // Local document vars
        setDocument,
            document,
            docElem,
            documentIsHTML,
            rbuggyQSA,
            rbuggyMatches,
            matches,
            contains,
            // Instance-specific data
        expando = "sizzle" + 1 * new Date(),
            preferredDoc = window.document,
            dirruns = 0,
            done = 0,
            classCache = createCache(),
            tokenCache = createCache(),
            compilerCache = createCache(),
            nonnativeSelectorCache = createCache(),
            sortOrder = function sortOrder(a, b) {
          if (a === b) {
            hasDuplicate = true;
          }

          return 0;
        },
            // Instance methods
        hasOwn = {}.hasOwnProperty,
            arr = [],
            pop = arr.pop,
            push_native = arr.push,
            push = arr.push,
            slice = arr.slice,
            // Use a stripped-down indexOf as it's faster than native
        // https://jsperf.com/thor-indexof-vs-for/5
        indexOf = function indexOf(list, elem) {
          var i = 0,
              len = list.length;

          for (; i < len; i++) {
            if (list[i] === elem) {
              return i;
            }
          }

          return -1;
        },
            booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
            // Regular expressions
        // http://www.w3.org/TR/css3-selectors/#whitespace
        whitespace = "[\\x20\\t\\r\\n\\f]",
            // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
        identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+",
            // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
        attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + // Operator (capture 2)
        "*([*^$|!~]?=)" + whitespace + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
        "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + "*\\]",
            pseudos = ":(" + identifier + ")(?:\\((" + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
        // 1. quoted (capture 3; capture 4 or capture 5)
        "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + // 2. simple (capture 6)
        "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + // 3. anything else (capture 2)
        ".*" + ")\\)|)",
            // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
        rwhitespace = new RegExp(whitespace + "+", "g"),
            rtrim = new RegExp("^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g"),
            rcomma = new RegExp("^" + whitespace + "*," + whitespace + "*"),
            rcombinators = new RegExp("^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*"),
            rpseudo = new RegExp(pseudos),
            ridentifier = new RegExp("^" + identifier + "$"),
            matchExpr = {
          "ID": new RegExp("^#(" + identifier + ")"),
          "CLASS": new RegExp("^\\.(" + identifier + ")"),
          "TAG": new RegExp("^(" + identifier + "|[*])"),
          "ATTR": new RegExp("^" + attributes),
          "PSEUDO": new RegExp("^" + pseudos),
          "CHILD": new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i"),
          "bool": new RegExp("^(?:" + booleans + ")$", "i"),
          // For use in libraries implementing .is()
          // We use this for POS matching in `select`
          "needsContext": new RegExp("^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i")
        },
            rnative = /^[^{]+\{\s*\[native \w/,
            // Easily-parseable/retrievable ID or TAG or CLASS selectors
        rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
            rsibling = /[+~]/,
            // CSS escapes
        // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
        runescape = new RegExp("\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig"),
            funescape = function funescape(_, escaped, escapedWhitespace) {
          var high = "0x" + escaped - 0x10000; // NaN means non-codepoint
          // Support: Firefox<24
          // Workaround erroneous numeric interpretation of +"0x"

          return high !== high || escapedWhitespace ? escaped : high < 0 ? // BMP codepoint
          String.fromCharCode(high + 0x10000) : // Supplemental Plane codepoint (surrogate pair)
          String.fromCharCode(high >> 10 | 0xD800, high & 0x3FF | 0xDC00);
        },
            // CSS string/identifier serialization
        // https://drafts.csswg.org/cssom/#common-serializing-idioms
        rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,
            fcssescape = function fcssescape(ch, asCodePoint) {
          if (asCodePoint) {
            // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
            if (ch === "\0") {
              return "\uFFFD";
            } // Control characters and (dependent upon position) numbers get escaped as code points


            return ch.slice(0, -1) + "\\" + ch.charCodeAt(ch.length - 1).toString(16) + " ";
          } // Other potentially-special ASCII characters get backslash-escaped


          return "\\" + ch;
        },
            // Used for iframes
        // See setDocument()
        // Removing the function wrapper causes a "Permission Denied"
        // error in IE
        unloadHandler = function unloadHandler() {
          setDocument();
        },
            inDisabledFieldset = addCombinator(function (elem) {
          return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset";
        }, {
          dir: "parentNode",
          next: "legend"
        }); // Optimize for push.apply( _, NodeList )


        try {
          push.apply(arr = slice.call(preferredDoc.childNodes), preferredDoc.childNodes); // Support: Android<4.0
          // Detect silently failing push.apply

          arr[preferredDoc.childNodes.length].nodeType;
        } catch (e) {
          push = {
            apply: arr.length ? // Leverage slice if possible
            function (target, els) {
              push_native.apply(target, slice.call(els));
            } : // Support: IE<9
            // Otherwise append directly
            function (target, els) {
              var j = target.length,
                  i = 0; // Can't trust NodeList.length

              while (target[j++] = els[i++]) {}

              target.length = j - 1;
            }
          };
        }

        function Sizzle(selector, context, results, seed) {
          var m,
              i,
              elem,
              nid,
              match,
              groups,
              newSelector,
              newContext = context && context.ownerDocument,
              // nodeType defaults to 9, since context defaults to document
          nodeType = context ? context.nodeType : 9;
          results = results || []; // Return early from calls with invalid selector or context

          if (typeof selector !== "string" || !selector || nodeType !== 1 && nodeType !== 9 && nodeType !== 11) {
            return results;
          } // Try to shortcut find operations (as opposed to filters) in HTML documents


          if (!seed) {
            if ((context ? context.ownerDocument || context : preferredDoc) !== document) {
              setDocument(context);
            }

            context = context || document;

            if (documentIsHTML) {
              // If the selector is sufficiently simple, try using a "get*By*" DOM method
              // (excepting DocumentFragment context, where the methods don't exist)
              if (nodeType !== 11 && (match = rquickExpr.exec(selector))) {
                // ID selector
                if (m = match[1]) {
                  // Document context
                  if (nodeType === 9) {
                    if (elem = context.getElementById(m)) {
                      // Support: IE, Opera, Webkit
                      // TODO: identify versions
                      // getElementById can match elements by name instead of ID
                      if (elem.id === m) {
                        results.push(elem);
                        return results;
                      }
                    } else {
                      return results;
                    } // Element context

                  } else {
                    // Support: IE, Opera, Webkit
                    // TODO: identify versions
                    // getElementById can match elements by name instead of ID
                    if (newContext && (elem = newContext.getElementById(m)) && contains(context, elem) && elem.id === m) {
                      results.push(elem);
                      return results;
                    }
                  } // Type selector

                } else if (match[2]) {
                  push.apply(results, context.getElementsByTagName(selector));
                  return results; // Class selector
                } else if ((m = match[3]) && support.getElementsByClassName && context.getElementsByClassName) {
                  push.apply(results, context.getElementsByClassName(m));
                  return results;
                }
              } // Take advantage of querySelectorAll


              if (support.qsa && !nonnativeSelectorCache[selector + " "] && (!rbuggyQSA || !rbuggyQSA.test(selector))) {
                if (nodeType !== 1) {
                  newContext = context;
                  newSelector = selector; // qSA looks outside Element context, which is not what we want
                  // Thanks to Andrew Dupont for this workaround technique
                  // Support: IE <=8
                  // Exclude object elements
                } else if (context.nodeName.toLowerCase() !== "object") {
                  // Capture the context ID, setting it first if necessary
                  if (nid = context.getAttribute("id")) {
                    nid = nid.replace(rcssescape, fcssescape);
                  } else {
                    context.setAttribute("id", nid = expando);
                  } // Prefix every selector in the list


                  groups = tokenize(selector);
                  i = groups.length;

                  while (i--) {
                    groups[i] = "#" + nid + " " + toSelector(groups[i]);
                  }

                  newSelector = groups.join(","); // Expand context for sibling selectors

                  newContext = rsibling.test(selector) && testContext(context.parentNode) || context;
                }

                if (newSelector) {
                  try {
                    if (newSelector.indexOf(':has(') > -1) {
                      // https://github.com/AdguardTeam/ExtendedCss/issues/149
                      throw new Error('Do not handle :has() pseudo-class by the native method');
                    }

                    push.apply(results, newContext.querySelectorAll(newSelector));
                    return results;
                  } catch (qsaError) {
                    // [AdGuard Path]: Fix the cache value
                    nonnativeSelectorCache(selector, true);
                  } finally {
                    if (nid === expando) {
                      context.removeAttribute("id");
                    }
                  }
                }
              }
            }
          } // All others


          return select(selector.replace(rtrim, "$1"), context, results, seed);
        }
        /**
         * Create key-value caches of limited size
         * @returns {function(string, object)} Returns the Object data after storing it on itself with
         *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
         *	deleting the oldest entry
         */


        function createCache() {
          var keys = [];

          function cache(key, value) {
            // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
            if (keys.push(key + " ") > Expr.cacheLength) {
              // Only keep the most recent entries
              delete cache[keys.shift()];
            }

            return cache[key + " "] = value;
          }

          return cache;
        }
        /**
         * Mark a function for special use by Sizzle
         * @param {Function} fn The function to mark
         */


        function markFunction(fn) {
          fn[expando] = true;
          return fn;
        }
        /**
         * Support testing using an element
         * @param {Function} fn Passed the created element and returns a boolean result
         */


        function assert(fn) {
          var el = document.createElement("fieldset");

          try {
            return !!fn(el);
          } catch (e) {
            return false;
          } finally {
            // Remove from its parent by default
            if (el.parentNode) {
              el.parentNode.removeChild(el);
            } // release memory in IE


            el = null;
          }
        }
        /**
         * Adds the same handler for all of the specified attrs
         * @param {String} attrs Pipe-separated list of attributes
         * @param {Function} handler The method that will be applied
         */


        function addHandle(attrs, handler) {
          var arr = attrs.split("|"),
              i = arr.length;

          while (i--) {
            Expr.attrHandle[arr[i]] = handler;
          }
        }
        /**
         * Checks document order of two siblings
         * @param {Element} a
         * @param {Element} b
         * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
         */


        function siblingCheck(a, b) {
          var cur = b && a,
              diff = cur && a.nodeType === 1 && b.nodeType === 1 && a.sourceIndex - b.sourceIndex; // Use IE sourceIndex if available on both nodes

          if (diff) {
            return diff;
          } // Check if b follows a


          if (cur) {
            while (cur = cur.nextSibling) {
              if (cur === b) {
                return -1;
              }
            }
          }

          return a ? 1 : -1;
        }
        /**
         * Returns a function to use in pseudos for :enabled/:disabled
         * @param {Boolean} disabled true for :disabled; false for :enabled
         */


        function createDisabledPseudo(disabled) {
          // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable
          return function (elem) {
            // Only certain elements can match :enabled or :disabled
            // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
            // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
            if ("form" in elem) {
              // Check for inherited disabledness on relevant non-disabled elements:
              // * listed form-associated elements in a disabled fieldset
              //   https://html.spec.whatwg.org/multipage/forms.html#category-listed
              //   https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
              // * option elements in a disabled optgroup
              //   https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
              // All such elements have a "form" property.
              if (elem.parentNode && elem.disabled === false) {
                // Option elements defer to a parent optgroup if present
                if ("label" in elem) {
                  if ("label" in elem.parentNode) {
                    return elem.parentNode.disabled === disabled;
                  } else {
                    return elem.disabled === disabled;
                  }
                } // Support: IE 6 - 11
                // Use the isDisabled shortcut property to check for disabled fieldset ancestors


                return elem.isDisabled === disabled || // Where there is no isDisabled, check manually

                /* jshint -W018 */
                elem.isDisabled !== !disabled && inDisabledFieldset(elem) === disabled;
              }

              return elem.disabled === disabled; // Try to winnow out elements that can't be disabled before trusting the disabled property.
              // Some victims get caught in our net (label, legend, menu, track), but it shouldn't
              // even exist on them, let alone have a boolean value.
            } else if ("label" in elem) {
              return elem.disabled === disabled;
            } // Remaining elements are neither :enabled nor :disabled


            return false;
          };
        }
        /**
         * Checks a node for validity as a Sizzle context
         * @param {Element|Object=} context
         * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
         */


        function testContext(context) {
          return context && typeof context.getElementsByTagName !== "undefined" && context;
        } // Expose support vars for convenience


        support = Sizzle.support = {};
        /**
         * Detects XML nodes
         * @param {Element|Object} elem An element or a document
         * @returns {Boolean} True iff elem is a non-HTML XML node
         */

        isXML = Sizzle.isXML = function (elem) {
          // documentElement is verified for cases where it doesn't yet exist
          // (such as loading iframes in IE - #4833)
          var documentElement = elem && (elem.ownerDocument || elem).documentElement;
          return documentElement ? documentElement.nodeName !== "HTML" : false;
        };
        /**
         * Sets document-related variables once based on the current document
         * @param {Element|Object} [doc] An element or document object to use to set the document
         * @returns {Object} Returns the current document
         */


        setDocument = Sizzle.setDocument = function (node) {
          var hasCompare,
              subWindow,
              doc = node ? node.ownerDocument || node : preferredDoc; // Return early if doc is invalid or already selected

          if (doc === document || doc.nodeType !== 9 || !doc.documentElement) {
            return document;
          } // Update global variables


          document = doc;
          docElem = document.documentElement;
          documentIsHTML = !isXML(document); // Support: IE 9-11, Edge
          // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936)

          if (preferredDoc !== document && (subWindow = document.defaultView) && subWindow.top !== subWindow) {
            // Support: IE 11, Edge
            if (subWindow.addEventListener) {
              subWindow.addEventListener("unload", unloadHandler, false); // Support: IE 9 - 10 only
            } else if (subWindow.attachEvent) {
              subWindow.attachEvent("onunload", unloadHandler);
            }
          }
          /* Attributes
          ---------------------------------------------------------------------- */
          // Support: IE<8
          // Verify that getAttribute really returns attributes and not properties
          // (excepting IE8 booleans)


          support.attributes = assert(function (el) {
            el.className = "i";
            return !el.getAttribute("className");
          });
          /* getElement(s)By*
          ---------------------------------------------------------------------- */
          // Check if getElementsByTagName("*") returns only elements

          support.getElementsByTagName = assert(function (el) {
            el.appendChild(document.createComment(""));
            return !el.getElementsByTagName("*").length;
          }); // Support: IE<9

          support.getElementsByClassName = rnative.test(document.getElementsByClassName); // Support: IE<10
          // Check if getElementById returns elements by name
          // The broken getElementById methods don't pick up programmatically-set names,
          // so use a roundabout getElementsByName test

          support.getById = assert(function (el) {
            docElem.appendChild(el).id = expando;
            return !document.getElementsByName || !document.getElementsByName(expando).length;
          }); // ID filter and find

          if (support.getById) {
            Expr.filter["ID"] = function (id) {
              var attrId = id.replace(runescape, funescape);
              return function (elem) {
                return elem.getAttribute("id") === attrId;
              };
            };

            Expr.find["ID"] = function (id, context) {
              if (typeof context.getElementById !== "undefined" && documentIsHTML) {
                var elem = context.getElementById(id);
                return elem ? [elem] : [];
              }
            };
          } else {
            Expr.filter["ID"] = function (id) {
              var attrId = id.replace(runescape, funescape);
              return function (elem) {
                var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
                return node && node.value === attrId;
              };
            }; // Support: IE 6 - 7 only
            // getElementById is not reliable as a find shortcut


            Expr.find["ID"] = function (id, context) {
              if (typeof context.getElementById !== "undefined" && documentIsHTML) {
                var node,
                    i,
                    elems,
                    elem = context.getElementById(id);

                if (elem) {
                  // Verify the id attribute
                  node = elem.getAttributeNode("id");

                  if (node && node.value === id) {
                    return [elem];
                  } // Fall back on getElementsByName


                  elems = context.getElementsByName(id);
                  i = 0;

                  while (elem = elems[i++]) {
                    node = elem.getAttributeNode("id");

                    if (node && node.value === id) {
                      return [elem];
                    }
                  }
                }

                return [];
              }
            };
          } // Tag


          Expr.find["TAG"] = support.getElementsByTagName ? function (tag, context) {
            if (typeof context.getElementsByTagName !== "undefined") {
              return context.getElementsByTagName(tag); // DocumentFragment nodes don't have gEBTN
            } else if (support.qsa) {
              return context.querySelectorAll(tag);
            }
          } : function (tag, context) {
            var elem,
                tmp = [],
                i = 0,
                // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
            results = context.getElementsByTagName(tag); // Filter out possible comments

            if (tag === "*") {
              while (elem = results[i++]) {
                if (elem.nodeType === 1) {
                  tmp.push(elem);
                }
              }

              return tmp;
            }

            return results;
          }; // Class

          Expr.find["CLASS"] = support.getElementsByClassName && function (className, context) {
            if (typeof context.getElementsByClassName !== "undefined" && documentIsHTML) {
              return context.getElementsByClassName(className);
            }
          };
          /* QSA/matchesSelector
          ---------------------------------------------------------------------- */
          // QSA and matchesSelector support
          // matchesSelector(:active) reports false when true (IE9/Opera 11.5)


          rbuggyMatches = []; // qSa(:focus) reports false when true (Chrome 21)
          // We allow this because of a bug in IE8/9 that throws an error
          // whenever `document.activeElement` is accessed on an iframe
          // So, we allow :focus to pass through QSA all the time to avoid the IE error
          // See https://bugs.jquery.com/ticket/13378

          rbuggyQSA = [];

          if (support.qsa = rnative.test(document.querySelectorAll)) {
            // Build QSA regex
            // Regex strategy adopted from Diego Perini
            assert(function (el) {
              // Select is set to empty string on purpose
              // This is to test IE's treatment of not explicitly
              // setting a boolean content attribute,
              // since its presence should be enough
              // https://bugs.jquery.com/ticket/12359
              docElem.appendChild(el).innerHTML = AGPolicy.createHTML("<a id='" + expando + "'></a>" + "<select id='" + expando + "-\r\\' msallowcapture=''>" + "<option selected=''></option></select>"); // Support: IE8, Opera 11-12.16
              // Nothing should be selected when empty strings follow ^= or $= or *=
              // The test attribute must be unknown in Opera but "safe" for WinRT
              // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section

              if (el.querySelectorAll("[msallowcapture^='']").length) {
                rbuggyQSA.push("[*^$]=" + whitespace + "*(?:''|\"\")");
              } // Support: IE8
              // Boolean attributes and "value" are not treated correctly


              if (!el.querySelectorAll("[selected]").length) {
                rbuggyQSA.push("\\[" + whitespace + "*(?:value|" + booleans + ")");
              } // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+


              if (!el.querySelectorAll("[id~=" + expando + "-]").length) {
                rbuggyQSA.push("~=");
              } // Webkit/Opera - :checked should return selected option elements
              // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
              // IE8 throws error here and will not see later tests


              if (!el.querySelectorAll(":checked").length) {
                rbuggyQSA.push(":checked");
              } // Support: Safari 8+, iOS 8+
              // https://bugs.webkit.org/show_bug.cgi?id=136851
              // In-page `selector#id sibling-combinator selector` fails


              if (!el.querySelectorAll("a#" + expando + "+*").length) {
                rbuggyQSA.push(".#.+[+~]");
              }
            });
            assert(function (el) {
              el.innerHTML = AGPolicy.createHTML("<a href='' disabled='disabled'></a>" + "<select disabled='disabled'><option/></select>"); // Support: Windows 8 Native Apps
              // The type and name attributes are restricted during .innerHTML assignment

              var input = document.createElement("input");
              input.setAttribute("type", "hidden");
              el.appendChild(input).setAttribute("name", "D"); // Support: IE8
              // Enforce case-sensitivity of name attribute

              if (el.querySelectorAll("[name=d]").length) {
                rbuggyQSA.push("name" + whitespace + "*[*^$|!~]?=");
              } // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
              // IE8 throws error here and will not see later tests


              if (el.querySelectorAll(":enabled").length !== 2) {
                rbuggyQSA.push(":enabled", ":disabled");
              } // Support: IE9-11+
              // IE's :disabled selector does not pick up the children of disabled fieldsets


              docElem.appendChild(el).disabled = true;

              if (el.querySelectorAll(":disabled").length !== 2) {
                rbuggyQSA.push(":enabled", ":disabled");
              } // Opera 10-11 does not throw on post-comma invalid pseudos


              el.querySelectorAll("*,:x");
              rbuggyQSA.push(",.*:");
            });
          }

          if (support.matchesSelector = rnative.test(matches = docElem.matches || docElem.webkitMatchesSelector || docElem.mozMatchesSelector || docElem.oMatchesSelector || docElem.msMatchesSelector)) {
            assert(function (el) {
              // Check to see if it's possible to do matchesSelector
              // on a disconnected node (IE 9)
              support.disconnectedMatch = matches.call(el, "*"); // This should fail with an exception
              // Gecko does not error, returns false instead

              matches.call(el, "[s!='']:x");
              rbuggyMatches.push("!=", pseudos);
            });
          }

          rbuggyQSA = rbuggyQSA.length && new RegExp(rbuggyQSA.join("|"));
          rbuggyMatches = rbuggyMatches.length && new RegExp(rbuggyMatches.join("|"));
          /* Contains
          ---------------------------------------------------------------------- */

          hasCompare = rnative.test(docElem.compareDocumentPosition); // Element contains another
          // Purposefully self-exclusive
          // As in, an element does not contain itself

          contains = hasCompare || rnative.test(docElem.contains) ? function (a, b) {
            var adown = a.nodeType === 9 ? a.documentElement : a,
                bup = b && b.parentNode;
            return a === bup || !!(bup && bup.nodeType === 1 && (adown.contains ? adown.contains(bup) : a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16));
          } : function (a, b) {
            if (b) {
              while (b = b.parentNode) {
                if (b === a) {
                  return true;
                }
              }
            }

            return false;
          };
          /* Sorting
          ---------------------------------------------------------------------- */
          // Document order sorting

          sortOrder = hasCompare ? function (a, b) {
            // Flag for duplicate removal
            if (a === b) {
              hasDuplicate = true;
              return 0;
            } // Sort on method existence if only one input has compareDocumentPosition


            var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;

            if (compare) {
              return compare;
            } // Calculate position if both inputs belong to the same document


            compare = (a.ownerDocument || a) === (b.ownerDocument || b) ? a.compareDocumentPosition(b) : // Otherwise we know they are disconnected
            1; // Disconnected nodes

            if (compare & 1 || !support.sortDetached && b.compareDocumentPosition(a) === compare) {
              // Choose the first element that is related to our preferred document
              if (a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a)) {
                return -1;
              }

              if (b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b)) {
                return 1;
              } // Maintain original order


              return sortInput ? indexOf(sortInput, a) - indexOf(sortInput, b) : 0;
            }

            return compare & 4 ? -1 : 1;
          } : function (a, b) {
            // Exit early if the nodes are identical
            if (a === b) {
              hasDuplicate = true;
              return 0;
            }

            var cur,
                i = 0,
                aup = a.parentNode,
                bup = b.parentNode,
                ap = [a],
                bp = [b]; // Parentless nodes are either documents or disconnected

            if (!aup || !bup) {
              return a === document ? -1 : b === document ? 1 : aup ? -1 : bup ? 1 : sortInput ? indexOf(sortInput, a) - indexOf(sortInput, b) : 0; // If the nodes are siblings, we can do a quick check
            } else if (aup === bup) {
              return siblingCheck(a, b);
            } // Otherwise we need full lists of their ancestors for comparison


            cur = a;

            while (cur = cur.parentNode) {
              ap.unshift(cur);
            }

            cur = b;

            while (cur = cur.parentNode) {
              bp.unshift(cur);
            } // Walk down the tree looking for a discrepancy


            while (ap[i] === bp[i]) {
              i++;
            }

            return i ? // Do a sibling check if the nodes have a common ancestor
            siblingCheck(ap[i], bp[i]) : // Otherwise nodes in our document sort first
            ap[i] === preferredDoc ? -1 : bp[i] === preferredDoc ? 1 : 0;
          };
          return document;
        };

        Sizzle.matches = function (expr, elements) {
          return Sizzle(expr, null, null, elements);
        };

        Sizzle.matchesSelector = function (elem, expr) {
          // Set document vars if needed
          if ((elem.ownerDocument || elem) !== document) {
            setDocument(elem);
          }

          if (support.matchesSelector && documentIsHTML && !nonnativeSelectorCache[expr + " "] && (!rbuggyMatches || !rbuggyMatches.test(expr)) && (!rbuggyQSA || !rbuggyQSA.test(expr))) {
            try {
              var ret = matches.call(elem, expr); // IE 9's matchesSelector returns false on disconnected nodes

              if (ret || support.disconnectedMatch || // As well, disconnected nodes are said to be in a document
              // fragment in IE 9
              elem.document && elem.document.nodeType !== 11) {
                return ret;
              }
            } catch (e) {
              // [AdGuard Path]: Fix the cache value
              nonnativeSelectorCache(expr, true);
            }
          }

          return Sizzle(expr, document, null, [elem]).length > 0;
        };

        Sizzle.contains = function (context, elem) {
          // Set document vars if needed
          if ((context.ownerDocument || context) !== document) {
            setDocument(context);
          }

          return contains(context, elem);
        };

        Sizzle.attr = function (elem, name) {
          // Set document vars if needed
          if ((elem.ownerDocument || elem) !== document) {
            setDocument(elem);
          }

          var fn = Expr.attrHandle[name.toLowerCase()],
              // Don't get fooled by Object.prototype properties (jQuery #13807)
          val = fn && hasOwn.call(Expr.attrHandle, name.toLowerCase()) ? fn(elem, name, !documentIsHTML) : undefined;
          return val !== undefined ? val : support.attributes || !documentIsHTML ? elem.getAttribute(name) : (val = elem.getAttributeNode(name)) && val.specified ? val.value : null;
        };

        Sizzle.escape = function (sel) {
          return (sel + "").replace(rcssescape, fcssescape);
        };

        Sizzle.error = function (msg) {
          throw new Error("Syntax error, unrecognized expression: " + msg);
        };
        /**
         * Document sorting and removing duplicates
         * @param {ArrayLike} results
         */


        Sizzle.uniqueSort = function (results) {
          var elem,
              duplicates = [],
              j = 0,
              i = 0; // Unless we *know* we can detect duplicates, assume their presence

          hasDuplicate = !support.detectDuplicates;
          sortInput = !support.sortStable && results.slice(0);
          results.sort(sortOrder);

          if (hasDuplicate) {
            while (elem = results[i++]) {
              if (elem === results[i]) {
                j = duplicates.push(i);
              }
            }

            while (j--) {
              results.splice(duplicates[j], 1);
            }
          } // Clear input after sorting to release objects
          // See https://github.com/jquery/sizzle/pull/225


          sortInput = null;
          return results;
        };
        /**
         * Utility function for retrieving the text value of an array of DOM nodes
         * @param {Array|Element} elem
         */


        getText = Sizzle.getText = function (elem) {
          var node,
              ret = "",
              i = 0,
              nodeType = elem.nodeType;

          if (!nodeType) {
            // If no nodeType, this is expected to be an array
            while (node = elem[i++]) {
              // Do not traverse comment nodes
              ret += getText(node);
            }
          } else if (nodeType === 1 || nodeType === 9 || nodeType === 11) {
            // Use textContent for elements
            // innerText usage removed for consistency of new lines (jQuery #11153)
            if (typeof elem.textContent === "string") {
              return elem.textContent;
            } else {
              // Traverse its children
              for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
                ret += getText(elem);
              }
            }
          } else if (nodeType === 3 || nodeType === 4) {
            return elem.nodeValue;
          } // Do not include comment or processing instruction nodes


          return ret;
        };

        Expr = Sizzle.selectors = {
          // Can be adjusted by the user
          cacheLength: 50,
          createPseudo: markFunction,
          match: matchExpr,
          attrHandle: {},
          find: {},
          relative: {
            ">": {
              dir: "parentNode",
              first: true
            },
            " ": {
              dir: "parentNode"
            },
            "+": {
              dir: "previousSibling",
              first: true
            },
            "~": {
              dir: "previousSibling"
            }
          },
          preFilter: {
            "ATTR": function ATTR(match) {
              match[1] = match[1].replace(runescape, funescape); // Move the given value to match[3] whether quoted or unquoted

              match[3] = (match[3] || match[4] || match[5] || "").replace(runescape, funescape);

              if (match[2] === "~=") {
                match[3] = " " + match[3] + " ";
              }

              return match.slice(0, 4);
            },
            "CHILD": function CHILD(match) {
              /* matches from matchExpr["CHILD"]
              	1 type (only|nth|...)
              	2 what (child|of-type)
              	3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
              	4 xn-component of xn+y argument ([+-]?\d*n|)
              	5 sign of xn-component
              	6 x of xn-component
              	7 sign of y-component
              	8 y of y-component
              */
              match[1] = match[1].toLowerCase();

              if (match[1].slice(0, 3) === "nth") {
                // nth-* requires argument
                if (!match[3]) {
                  Sizzle.error(match[0]);
                } // numeric x and y parameters for Expr.filter.CHILD
                // remember that false/true cast respectively to 0/1


                match[4] = +(match[4] ? match[5] + (match[6] || 1) : 2 * (match[3] === "even" || match[3] === "odd"));
                match[5] = +(match[7] + match[8] || match[3] === "odd"); // other types prohibit arguments
              } else if (match[3]) {
                Sizzle.error(match[0]);
              }

              return match;
            },
            "PSEUDO": function PSEUDO(match) {
              var excess,
                  unquoted = !match[6] && match[2];

              if (matchExpr["CHILD"].test(match[0])) {
                return null;
              } // Accept quoted arguments as-is


              if (match[3]) {
                match[2] = match[4] || match[5] || ""; // Strip excess characters from unquoted arguments
              } else if (unquoted && rpseudo.test(unquoted) && ( // Get excess from tokenize (recursively)
              excess = tokenize(unquoted, true)) && ( // advance to the next closing parenthesis
              excess = unquoted.indexOf(")", unquoted.length - excess) - unquoted.length)) {
                // excess is a negative index
                match[0] = match[0].slice(0, excess);
                match[2] = unquoted.slice(0, excess);
              } // Return only captures needed by the pseudo filter method (type and argument)


              return match.slice(0, 3);
            }
          },
          filter: {
            "TAG": function TAG(nodeNameSelector) {
              var nodeName = nodeNameSelector.replace(runescape, funescape).toLowerCase();
              return nodeNameSelector === "*" ? function () {
                return true;
              } : function (elem) {
                return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
              };
            },
            "CLASS": function CLASS(className) {
              var pattern = classCache[className + " "];
              return pattern || (pattern = new RegExp("(^|" + whitespace + ")" + className + "(" + whitespace + "|$)")) && classCache(className, function (elem) {
                return pattern.test(typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "");
              });
            },
            "ATTR": function ATTR(name, operator, check) {
              return function (elem) {
                var result = Sizzle.attr(elem, name);

                if (result == null) {
                  return operator === "!=";
                }

                if (!operator) {
                  return true;
                }

                result += "";
                return operator === "=" ? result === check : operator === "!=" ? result !== check : operator === "^=" ? check && result.indexOf(check) === 0 : operator === "*=" ? check && result.indexOf(check) > -1 : operator === "$=" ? check && result.slice(-check.length) === check : operator === "~=" ? (" " + result.replace(rwhitespace, " ") + " ").indexOf(check) > -1 : operator === "|=" ? result === check || result.slice(0, check.length + 1) === check + "-" : false;
              };
            },
            "CHILD": function CHILD(type, what, argument, first, last) {
              var simple = type.slice(0, 3) !== "nth",
                  forward = type.slice(-4) !== "last",
                  ofType = what === "of-type";
              return first === 1 && last === 0 ? // Shortcut for :nth-*(n)
              function (elem) {
                return !!elem.parentNode;
              } : function (elem, context, xml) {
                var cache,
                    uniqueCache,
                    outerCache,
                    node,
                    nodeIndex,
                    start,
                    dir = simple !== forward ? "nextSibling" : "previousSibling",
                    parent = elem.parentNode,
                    name = ofType && elem.nodeName.toLowerCase(),
                    useCache = !xml && !ofType,
                    diff = false;

                if (parent) {
                  // :(first|last|only)-(child|of-type)
                  if (simple) {
                    while (dir) {
                      node = elem;

                      while (node = node[dir]) {
                        if (ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1) {
                          return false;
                        }
                      } // Reverse direction for :only-* (if we haven't yet done so)


                      start = dir = type === "only" && !start && "nextSibling";
                    }

                    return true;
                  }

                  start = [forward ? parent.firstChild : parent.lastChild]; // non-xml :nth-child(...) stores cache data on `parent`

                  if (forward && useCache) {
                    // Seek `elem` from a previously-cached index
                    // ...in a gzip-friendly way
                    node = parent;
                    outerCache = node[expando] || (node[expando] = {}); // Support: IE <9 only
                    // Defend against cloned attroperties (jQuery gh-1709)

                    uniqueCache = outerCache[node.uniqueID] || (outerCache[node.uniqueID] = {});
                    cache = uniqueCache[type] || [];
                    nodeIndex = cache[0] === dirruns && cache[1];
                    diff = nodeIndex && cache[2];
                    node = nodeIndex && parent.childNodes[nodeIndex];

                    while (node = ++nodeIndex && node && node[dir] || ( // Fallback to seeking `elem` from the start
                    diff = nodeIndex = 0) || start.pop()) {
                      // When found, cache indexes on `parent` and break
                      if (node.nodeType === 1 && ++diff && node === elem) {
                        uniqueCache[type] = [dirruns, nodeIndex, diff];
                        break;
                      }
                    }
                  } else {
                    // Use previously-cached element index if available
                    if (useCache) {
                      // ...in a gzip-friendly way
                      node = elem;
                      outerCache = node[expando] || (node[expando] = {}); // Support: IE <9 only
                      // Defend against cloned attroperties (jQuery gh-1709)

                      uniqueCache = outerCache[node.uniqueID] || (outerCache[node.uniqueID] = {});
                      cache = uniqueCache[type] || [];
                      nodeIndex = cache[0] === dirruns && cache[1];
                      diff = nodeIndex;
                    } // xml :nth-child(...)
                    // or :nth-last-child(...) or :nth(-last)?-of-type(...)


                    if (diff === false) {
                      // Use the same loop as above to seek `elem` from the start
                      while (node = ++nodeIndex && node && node[dir] || (diff = nodeIndex = 0) || start.pop()) {
                        if ((ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1) && ++diff) {
                          // Cache the index of each encountered element
                          if (useCache) {
                            outerCache = node[expando] || (node[expando] = {}); // Support: IE <9 only
                            // Defend against cloned attroperties (jQuery gh-1709)

                            uniqueCache = outerCache[node.uniqueID] || (outerCache[node.uniqueID] = {});
                            uniqueCache[type] = [dirruns, diff];
                          }

                          if (node === elem) {
                            break;
                          }
                        }
                      }
                    }
                  } // Incorporate the offset, then check against cycle size


                  diff -= last;
                  return diff === first || diff % first === 0 && diff / first >= 0;
                }
              };
            },
            "PSEUDO": function PSEUDO(pseudo, argument) {
              // pseudo-class names are case-insensitive
              // http://www.w3.org/TR/selectors/#pseudo-classes
              // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
              // Remember that setFilters inherits from pseudos
              var args,
                  fn = Expr.pseudos[pseudo] || Expr.setFilters[pseudo.toLowerCase()] || Sizzle.error("unsupported pseudo: " + pseudo); // The user may use createPseudo to indicate that
              // arguments are needed to create the filter function
              // just as Sizzle does

              if (fn[expando]) {
                return fn(argument);
              } // But maintain support for old signatures


              if (fn.length > 1) {
                args = [pseudo, pseudo, "", argument];
                return Expr.setFilters.hasOwnProperty(pseudo.toLowerCase()) ? markFunction(function (seed, matches) {
                  var idx,
                      matched = fn(seed, argument),
                      i = matched.length;

                  while (i--) {
                    idx = indexOf(seed, matched[i]);
                    seed[idx] = !(matches[idx] = matched[i]);
                  }
                }) : function (elem) {
                  return fn(elem, 0, args);
                };
              }

              return fn;
            }
          },
          pseudos: {
            // Potentially complex pseudos
            "not": markFunction(function (selector) {
              // Trim the selector passed to compile
              // to avoid treating leading and trailing
              // spaces as combinators
              var input = [],
                  results = [],
                  matcher = compile(selector.replace(rtrim, "$1"));
              return matcher[expando] ? markFunction(function (seed, matches, context, xml) {
                var elem,
                    unmatched = matcher(seed, null, xml, []),
                    i = seed.length; // Match elements unmatched by `matcher`

                while (i--) {
                  if (elem = unmatched[i]) {
                    seed[i] = !(matches[i] = elem);
                  }
                }
              }) : function (elem, context, xml) {
                input[0] = elem;
                matcher(input, null, xml, results); // Don't keep the element (issue #299)

                input[0] = null;
                return !results.pop();
              };
            }),
            "has": markFunction(function (selector) {
              if (typeof selector === "string") {
                Sizzle.compile(selector);
              }

              return function (elem) {
                return Sizzle(selector, elem).length > 0;
              };
            }),
            // Removed :contains pseudo-class declaration
            // "Whether an element is represented by a :lang() selector
            // is based solely on the element's language value
            // being equal to the identifier C,
            // or beginning with the identifier C immediately followed by "-".
            // The matching of C against the element's language value is performed case-insensitively.
            // The identifier C does not have to be a valid language name."
            // http://www.w3.org/TR/selectors/#lang-pseudo
            "lang": markFunction(function (lang) {
              // lang value must be a valid identifier
              if (!ridentifier.test(lang || "")) {
                Sizzle.error("unsupported lang: " + lang);
              }

              lang = lang.replace(runescape, funescape).toLowerCase();
              return function (elem) {
                var elemLang;

                do {
                  if (elemLang = documentIsHTML ? elem.lang : elem.getAttribute("xml:lang") || elem.getAttribute("lang")) {
                    elemLang = elemLang.toLowerCase();
                    return elemLang === lang || elemLang.indexOf(lang + "-") === 0;
                  }
                } while ((elem = elem.parentNode) && elem.nodeType === 1);

                return false;
              };
            }),
            // Miscellaneous
            "target": function target(elem) {
              var hash = window.location && window.location.hash;
              return hash && hash.slice(1) === elem.id;
            },
            "root": function root(elem) {
              return elem === docElem;
            },
            "focus": function focus(elem) {
              return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
            },
            // Boolean properties
            "enabled": createDisabledPseudo(false),
            "disabled": createDisabledPseudo(true),
            "checked": function checked(elem) {
              // In CSS3, :checked should return both checked and selected elements
              // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
              var nodeName = elem.nodeName.toLowerCase();
              return nodeName === "input" && !!elem.checked || nodeName === "option" && !!elem.selected;
            },
            "selected": function selected(elem) {
              // Accessing this property makes selected-by-default
              // options in Safari work properly
              if (elem.parentNode) {
                elem.parentNode.selectedIndex;
              }

              return elem.selected === true;
            },
            // Contents
            "empty": function empty(elem) {
              // http://www.w3.org/TR/selectors/#empty-pseudo
              // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
              //   but not by others (comment: 8; processing instruction: 7; etc.)
              // nodeType < 6 works because attributes (2) do not appear as children
              for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
                if (elem.nodeType < 6) {
                  return false;
                }
              }

              return true;
            } // Removed custom pseudo-classes

          }
        }; // Removed custom pseudo-classes
        // Easy API for creating new setFilters

        function setFilters() {}

        setFilters.prototype = Expr.filters = Expr.pseudos;
        Expr.setFilters = new setFilters();
        /**
         * [AdGuard Patch]:
         * Sorts the tokens in order to mitigate the performance issues caused by matching slow pseudos first:
         * https://github.com/AdguardTeam/ExtendedCss/issues/55#issuecomment-364058745
         */

        var sortTokenGroups = function () {
          /**
           * Splits compound selector into a list of simple selectors
           *
           * @param {*} tokens Tokens to split into groups
           * @returns an array consisting of token groups (arrays) and relation tokens.
           */
          var splitCompoundSelector = function splitCompoundSelector(tokens) {
            var groups = [];
            var currentTokensGroup = [];
            var maxIdx = tokens.length - 1;

            for (var i = 0; i <= maxIdx; i++) {
              var token = tokens[i];
              var relative = Sizzle.selectors.relative[token.type];

              if (relative) {
                groups.push(currentTokensGroup);
                groups.push(token);
                currentTokensGroup = [];
              } else {
                currentTokensGroup.push(token);
              }

              if (i === maxIdx) {
                groups.push(currentTokensGroup);
              }
            }

            return groups;
          };

          var TOKEN_TYPES_VALUES = {
            // nth-child, etc, always go last
            "CHILD": 100,
            "ID": 90,
            "CLASS": 80,
            "TAG": 70,
            "ATTR": 70,
            "PSEUDO": 60
          };
          var POSITIONAL_PSEUDOS = ["nth", "first", "last", "eq", "even", "odd", "lt", "gt", "not"];
          /**
           * A function that defines the sort order.
           * Returns a value lesser than 0 if "left" is less than "right".
           */

          var compareFunction = function compareFunction(left, right) {
            var leftValue = TOKEN_TYPES_VALUES[left.type];
            var rightValue = TOKEN_TYPES_VALUES[right.type];
            return leftValue - rightValue;
          };
          /**
           * Checks if the specified tokens group is sortable.
           * We do not re-sort tokens in case of any positional or child pseudos in the group
           */


          var isSortable = function isSortable(tokens) {
            var iTokens = tokens.length;

            while (iTokens--) {
              var token = tokens[iTokens];

              if (token.type === "PSEUDO" && POSITIONAL_PSEUDOS.indexOf(token.matches[0]) !== -1) {
                return false;
              }

              if (token.type === "CHILD") {
                return false;
              }
            }

            return true;
          };
          /**
           * Sorts the tokens in order to mitigate the issues caused by the left-to-right matching.
           * The idea is change the tokens order so that Sizzle was matching fast selectors first (id, class),
           * and slow selectors after that (and here I mean our slow custom pseudo classes).
           *
           * @param {Array} tokens An array of tokens to sort
           * @returns {Array} A new re-sorted array
           */


          var sortTokens = function sortTokens(tokens) {
            if (!tokens || tokens.length === 1) {
              return tokens;
            }

            var sortedTokens = [];
            var groups = splitCompoundSelector(tokens);

            for (var i = 0; i < groups.length; i++) {
              var group = groups[i];

              if (group instanceof Array) {
                if (isSortable(group)) {
                  group.sort(compareFunction);
                }

                sortedTokens = sortedTokens.concat(group);
              } else {
                sortedTokens.push(group);
              }
            }

            return sortedTokens;
          };
          /**
           * Sorts every tokens array inside of the specified "groups" array.
           * See "sortTokens" methods for more information on how tokens are sorted.
           *
           * @param {Array} groups An array of tokens arrays.
           * @returns {Array} A new array that consists of the same tokens arrays after sorting
           */


          var sortTokenGroups = function sortTokenGroups(groups) {
            var sortedGroups = [];
            var len = groups.length;
            var i = 0;

            for (; i < len; i++) {
              sortedGroups.push(sortTokens(groups[i]));
            }

            return sortedGroups;
          }; // Expose


          return sortTokenGroups;
        }();
        /**
         * Creates custom policy to use TrustedTypes CSP policy
         * https://w3c.github.io/webappsec-trusted-types/dist/spec/
         */


        var AGPolicy = function createPolicy() {
          var defaultPolicy = {
            createHTML: function createHTML(input) {
              return input;
            },
            createScript: function createScript(input) {
              return input;
            },
            createScriptURL: function createScriptURL(input) {
              return input;
            }
          };

          if (window.trustedTypes && window.trustedTypes.createPolicy) {
            return window.trustedTypes.createPolicy("AGPolicy", defaultPolicy);
          }

          return defaultPolicy;
        }();
        /**
         * [AdGuard Patch]:
         * Removes trailing spaces from the tokens list
         *
         * @param {*} tokens An array of Sizzle tokens to post-process
         */


        function removeTrailingSpaces(tokens) {
          var iTokens = tokens.length;

          while (iTokens--) {
            var token = tokens[iTokens];

            if (token.type === " ") {
              tokens.length = iTokens;
            } else {
              break;
            }
          }
        }
        /**
         * [AdGuard Patch]:
         * An object with the information about selectors and their token representation
         * @typedef {{selectorText: string, groups: Array}} SelectorData
         * @property {string} selectorText A CSS selector text
         * @property {Array} groups An array of token groups corresponding to that selector
         */

        /**
         * [AdGuard Patch]:
         * This method processes parsed token groups, divides them into a number of selectors
         * and makes sure that each selector's tokens are cached properly in Sizzle.
         *
         * @param {*} groups Token groups (see {@link Sizzle.tokenize})
         * @returns {Array.<SelectorData>} An array of selectors data we got from the groups
         */


        function tokenGroupsToSelectors(groups) {
          // Remove trailing spaces which we can encounter in tolerant mode
          // We're doing it in tolerant mode only as this is the only case when
          // encountering trailing spaces is expected
          removeTrailingSpaces(groups[groups.length - 1]); // We need sorted tokens to make cache work properly

          var sortedGroups = sortTokenGroups(groups);
          var selectors = [];

          for (var i = 0; i < groups.length; i++) {
            var tokenGroups = groups[i];
            var selectorText = toSelector(tokenGroups);
            selectors.push({
              // Sizzle expects an array of token groups when compiling a selector
              groups: [tokenGroups],
              selectorText: selectorText
            }); // Now make sure that selector tokens are cached

            var tokensCacheItem = {
              groups: tokenGroups,
              sortedGroups: [sortedGroups[i]]
            };
            tokenCache(selectorText, tokensCacheItem);
          }

          return selectors;
        }
        /**
         * [AdGuard Patch]:
         * Add an additional argument for Sizzle.tokenize which indicates that it
         * should not throw on invalid tokens, and instead should return tokens
         * that it has produced so far.
         *
         * One more additional argument that allow to choose if you want to receive sorted or unsorted tokens
         * The problem is that the re-sorted selectors are valid for Sizzle, but not for the browser.
         * options.returnUnsorted -- return unsorted tokens if true.
         * options.cacheOnly -- return cached result only. Required for unit-tests.
         *
         * @param {*} options Optional configuration object with two additional flags
         * (options.tolerant, options.returnUnsorted, options.cacheOnly) -- see patches #5 and #6 notes
         */


        tokenize = Sizzle.tokenize = function (selector, parseOnly, options) {
          var matched,
              match,
              tokens,
              type,
              soFar,
              groups,
              preFilters,
              cached = tokenCache[selector + " "];
          var tolerant = options && options.tolerant;
          var returnUnsorted = options && options.returnUnsorted;
          var cacheOnly = options && options.cacheOnly;

          if (cached) {
            if (parseOnly) {
              return 0;
            } else {
              return (returnUnsorted ? cached.groups : cached.sortedGroups).slice(0);
            }
          }

          if (cacheOnly) {
            return null;
          }

          soFar = selector;
          groups = [];
          preFilters = Expr.preFilter;

          while (soFar) {
            // Comma and first run
            if (!matched || (match = rcomma.exec(soFar))) {
              if (match) {
                // Don't consume trailing commas as valid
                soFar = soFar.slice(match[0].length) || soFar;
              }

              groups.push(tokens = []);
            }

            matched = false; // Combinators

            if (match = rcombinators.exec(soFar)) {
              matched = match.shift();
              tokens.push({
                value: matched,
                // Cast descendant combinators to space
                type: match[0].replace(rtrim, " ")
              });
              soFar = soFar.slice(matched.length);
            } // Filters


            for (type in Expr.filter) {
              if ((match = matchExpr[type].exec(soFar)) && (!preFilters[type] || (match = preFilters[type](match)))) {
                matched = match.shift();
                tokens.push({
                  value: matched,
                  type: type,
                  matches: match
                });
                soFar = soFar.slice(matched.length);
              }
            }

            if (!matched) {
              break;
            }
          } // Return the length of the invalid excess
          // if we're just parsing
          // Otherwise, throw an error or return tokens


          var invalidLen = soFar.length;

          if (parseOnly) {
            return invalidLen;
          }

          if (invalidLen !== 0 && !tolerant) {
            Sizzle.error(selector); // Throws an error.
          }

          if (tolerant) {
            /**
             * [AdGuard Patch]:
             * In tolerant mode we return a special object that constists of
             * an array of parsed selectors (and their tokens) and a "nextIndex" field
             * that points to an index after which we're not able to parse selectors farther.
             */
            var nextIndex = selector.length - invalidLen;
            var selectors = tokenGroupsToSelectors(groups);
            return {
              selectors: selectors,
              nextIndex: nextIndex
            };
          }
          /** [AdGuard Patch]: Sorting tokens */


          var sortedGroups = sortTokenGroups(groups);
          /** [AdGuard Patch]: Change the way tokens are cached */

          var tokensCacheItem = {
            groups: groups,
            sortedGroups: sortedGroups
          };
          tokensCacheItem = tokenCache(selector, tokensCacheItem);
          return (returnUnsorted ? tokensCacheItem.groups : tokensCacheItem.sortedGroups).slice(0);
        };

        function toSelector(tokens) {
          var i = 0,
              len = tokens.length,
              selector = "";

          for (; i < len; i++) {
            selector += tokens[i].value;
          }

          return selector;
        }

        function addCombinator(matcher, combinator, base) {
          var dir = combinator.dir,
              skip = combinator.next,
              key = skip || dir,
              checkNonElements = base && key === "parentNode",
              doneName = done++;
          return combinator.first ? // Check against closest ancestor/preceding element
          function (elem, context, xml) {
            while (elem = elem[dir]) {
              if (elem.nodeType === 1 || checkNonElements) {
                return matcher(elem, context, xml);
              }
            }

            return false;
          } : // Check against all ancestor/preceding elements
          function (elem, context, xml) {
            var oldCache,
                uniqueCache,
                outerCache,
                newCache = [dirruns, doneName]; // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching

            if (xml) {
              while (elem = elem[dir]) {
                if (elem.nodeType === 1 || checkNonElements) {
                  if (matcher(elem, context, xml)) {
                    return true;
                  }
                }
              }
            } else {
              while (elem = elem[dir]) {
                if (elem.nodeType === 1 || checkNonElements) {
                  outerCache = elem[expando] || (elem[expando] = {}); // Support: IE <9 only
                  // Defend against cloned attroperties (jQuery gh-1709)

                  uniqueCache = outerCache[elem.uniqueID] || (outerCache[elem.uniqueID] = {});

                  if (skip && skip === elem.nodeName.toLowerCase()) {
                    elem = elem[dir] || elem;
                  } else if ((oldCache = uniqueCache[key]) && oldCache[0] === dirruns && oldCache[1] === doneName) {
                    // Assign to newCache so results back-propagate to previous elements
                    return newCache[2] = oldCache[2];
                  } else {
                    // Reuse newcache so results back-propagate to previous elements
                    uniqueCache[key] = newCache; // A match means we're done; a fail means we have to keep checking

                    if (newCache[2] = matcher(elem, context, xml)) {
                      return true;
                    }
                  }
                }
              }
            }

            return false;
          };
        }

        function elementMatcher(matchers) {
          return matchers.length > 1 ? function (elem, context, xml) {
            var i = matchers.length;

            while (i--) {
              if (!matchers[i](elem, context, xml)) {
                return false;
              }
            }

            return true;
          } : matchers[0];
        }

        function multipleContexts(selector, contexts, results) {
          var i = 0,
              len = contexts.length;

          for (; i < len; i++) {
            Sizzle(selector, contexts[i], results);
          }

          return results;
        }

        function condense(unmatched, map, filter, context, xml) {
          var elem,
              newUnmatched = [],
              i = 0,
              len = unmatched.length,
              mapped = map != null;

          for (; i < len; i++) {
            if (elem = unmatched[i]) {
              if (!filter || filter(elem, context, xml)) {
                newUnmatched.push(elem);

                if (mapped) {
                  map.push(i);
                }
              }
            }
          }

          return newUnmatched;
        }

        function setMatcher(preFilter, selector, matcher, postFilter, postFinder, postSelector) {
          if (postFilter && !postFilter[expando]) {
            postFilter = setMatcher(postFilter);
          }

          if (postFinder && !postFinder[expando]) {
            postFinder = setMatcher(postFinder, postSelector);
          }

          return markFunction(function (seed, results, context, xml) {
            var temp,
                i,
                elem,
                preMap = [],
                postMap = [],
                preexisting = results.length,
                // Get initial elements from seed or context
            elems = seed || multipleContexts(selector || "*", context.nodeType ? [context] : context, []),
                // Prefilter to get matcher input, preserving a map for seed-results synchronization
            matcherIn = preFilter && (seed || !selector) ? condense(elems, preMap, preFilter, context, xml) : elems,
                matcherOut = matcher ? // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
            postFinder || (seed ? preFilter : preexisting || postFilter) ? // ...intermediate processing is necessary
            [] : // ...otherwise use results directly
            results : matcherIn; // Find primary matches

            if (matcher) {
              matcher(matcherIn, matcherOut, context, xml);
            } // Apply postFilter


            if (postFilter) {
              temp = condense(matcherOut, postMap);
              postFilter(temp, [], context, xml); // Un-match failing elements by moving them back to matcherIn

              i = temp.length;

              while (i--) {
                if (elem = temp[i]) {
                  matcherOut[postMap[i]] = !(matcherIn[postMap[i]] = elem);
                }
              }
            }

            if (seed) {
              if (postFinder || preFilter) {
                if (postFinder) {
                  // Get the final matcherOut by condensing this intermediate into postFinder contexts
                  temp = [];
                  i = matcherOut.length;

                  while (i--) {
                    if (elem = matcherOut[i]) {
                      // Restore matcherIn since elem is not yet a final match
                      temp.push(matcherIn[i] = elem);
                    }
                  }

                  postFinder(null, matcherOut = [], temp, xml);
                } // Move matched elements from seed to results to keep them synchronized


                i = matcherOut.length;

                while (i--) {
                  if ((elem = matcherOut[i]) && (temp = postFinder ? indexOf(seed, elem) : preMap[i]) > -1) {
                    seed[temp] = !(results[temp] = elem);
                  }
                }
              } // Add elements to results, through postFinder if defined

            } else {
              matcherOut = condense(matcherOut === results ? matcherOut.splice(preexisting, matcherOut.length) : matcherOut);

              if (postFinder) {
                postFinder(null, results, matcherOut, xml);
              } else {
                push.apply(results, matcherOut);
              }
            }
          });
        }

        function matcherFromTokens(tokens) {
          var checkContext,
              matcher,
              j,
              len = tokens.length,
              leadingRelative = Expr.relative[tokens[0].type],
              implicitRelative = leadingRelative || Expr.relative[" "],
              i = leadingRelative ? 1 : 0,
              // The foundational matcher ensures that elements are reachable from top-level context(s)
          matchContext = addCombinator(function (elem) {
            return elem === checkContext;
          }, implicitRelative, true),
              matchAnyContext = addCombinator(function (elem) {
            return indexOf(checkContext, elem) > -1;
          }, implicitRelative, true),
              matchers = [function (elem, context, xml) {
            var ret = !leadingRelative && (xml || context !== outermostContext) || ((checkContext = context).nodeType ? matchContext(elem, context, xml) : matchAnyContext(elem, context, xml)); // Avoid hanging onto element (issue #299)

            checkContext = null;
            return ret;
          }];

          for (; i < len; i++) {
            if (matcher = Expr.relative[tokens[i].type]) {
              matchers = [addCombinator(elementMatcher(matchers), matcher)];
            } else {
              matcher = Expr.filter[tokens[i].type].apply(null, tokens[i].matches); // Return special upon seeing a positional matcher

              if (matcher[expando]) {
                // Find the next relative operator (if any) for proper handling
                j = ++i;

                for (; j < len; j++) {
                  if (Expr.relative[tokens[j].type]) {
                    break;
                  }
                }

                return setMatcher(i > 1 && elementMatcher(matchers), i > 1 && toSelector( // If the preceding token was a descendant combinator, insert an implicit any-element `*`
                tokens.slice(0, i - 1).concat({
                  value: tokens[i - 2].type === " " ? "*" : ""
                })).replace(rtrim, "$1"), matcher, i < j && matcherFromTokens(tokens.slice(i, j)), j < len && matcherFromTokens(tokens = tokens.slice(j)), j < len && toSelector(tokens));
              }

              matchers.push(matcher);
            }
          }

          return elementMatcher(matchers);
        }

        function matcherFromGroupMatchers(elementMatchers, setMatchers) {
          var bySet = setMatchers.length > 0,
              byElement = elementMatchers.length > 0,
              superMatcher = function superMatcher(seed, context, xml, results, outermost) {
            var elem,
                j,
                matcher,
                matchedCount = 0,
                i = "0",
                unmatched = seed && [],
                setMatched = [],
                contextBackup = outermostContext,
                // We must always have either seed elements or outermost context
            elems = seed || byElement && Expr.find["TAG"]("*", outermost),
                // Use integer dirruns iff this is the outermost matcher
            dirrunsUnique = dirruns += contextBackup == null ? 1 : Math.random() || 0.1,
                len = elems.length;

            if (outermost) {
              outermostContext = context === document || context || outermost;
            } // Add elements passing elementMatchers directly to results
            // Support: IE<9, Safari
            // Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id


            for (; i !== len && (elem = elems[i]) != null; i++) {
              if (byElement && elem) {
                j = 0;

                if (!context && elem.ownerDocument !== document) {
                  setDocument(elem);
                  xml = !documentIsHTML;
                }

                while (matcher = elementMatchers[j++]) {
                  if (matcher(elem, context || document, xml)) {
                    results.push(elem);
                    break;
                  }
                }

                if (outermost) {
                  dirruns = dirrunsUnique;
                }
              } // Track unmatched elements for set filters


              if (bySet) {
                // They will have gone through all possible matchers
                if (elem = !matcher && elem) {
                  matchedCount--;
                } // Lengthen the array for every element, matched or not


                if (seed) {
                  unmatched.push(elem);
                }
              }
            } // `i` is now the count of elements visited above, and adding it to `matchedCount`
            // makes the latter nonnegative.


            matchedCount += i; // Apply set filters to unmatched elements
            // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
            // equals `i`), unless we didn't visit _any_ elements in the above loop because we have
            // no element matchers and no seed.
            // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
            // case, which will result in a "00" `matchedCount` that differs from `i` but is also
            // numerically zero.

            if (bySet && i !== matchedCount) {
              j = 0;

              while (matcher = setMatchers[j++]) {
                matcher(unmatched, setMatched, context, xml);
              }

              if (seed) {
                // Reintegrate element matches to eliminate the need for sorting
                if (matchedCount > 0) {
                  while (i--) {
                    if (!(unmatched[i] || setMatched[i])) {
                      setMatched[i] = pop.call(results);
                    }
                  }
                } // Discard index placeholder values to get only actual matches


                setMatched = condense(setMatched);
              } // Add matches to results


              push.apply(results, setMatched); // Seedless set matches succeeding multiple successful matchers stipulate sorting

              if (outermost && !seed && setMatched.length > 0 && matchedCount + setMatchers.length > 1) {
                Sizzle.uniqueSort(results);
              }
            } // Override manipulation of globals by nested matchers


            if (outermost) {
              dirruns = dirrunsUnique;
              outermostContext = contextBackup;
            }

            return unmatched;
          };

          return bySet ? markFunction(superMatcher) : superMatcher;
        }

        compile = Sizzle.compile = function (selector, match
        /* Internal Use Only */
        ) {
          var i,
              setMatchers = [],
              elementMatchers = [],
              cached = compilerCache[selector + " "];

          if (!cached) {
            // Generate a function of recursive functions that can be used to check each element
            if (!match) {
              match = tokenize(selector);
            }

            i = match.length;

            while (i--) {
              cached = matcherFromTokens(match[i]);

              if (cached[expando]) {
                setMatchers.push(cached);
              } else {
                elementMatchers.push(cached);
              }
            } // Cache the compiled function


            cached = compilerCache(selector, matcherFromGroupMatchers(elementMatchers, setMatchers)); // Save selector and tokenization

            cached.selector = selector;
          }

          return cached;
        };
        /**
         * A low-level selection function that works with Sizzle's compiled
         *  selector functions
         * @param {String|Function} selector A selector or a pre-compiled
         *  selector function built with Sizzle.compile
         * @param {Element} context
         * @param {Array} [results]
         * @param {Array} [seed] A set of elements to match against
         */


        select = Sizzle.select = function (selector, context, results, seed) {
          var i,
              tokens,
              token,
              type,
              find,
              compiled = typeof selector === "function" && selector,
              match = !seed && tokenize(selector = compiled.selector || selector);
          results = results || []; // Try to minimize operations if there is only one selector in the list and no seed
          // (the latter of which guarantees us context)

          if (match.length === 1) {
            // Reduce context if the leading compound selector is an ID
            tokens = match[0] = match[0].slice(0);

            if (tokens.length > 2 && (token = tokens[0]).type === "ID" && context.nodeType === 9 && documentIsHTML && Expr.relative[tokens[1].type]) {
              context = (Expr.find["ID"](token.matches[0].replace(runescape, funescape), context) || [])[0];

              if (!context) {
                return results; // Precompiled matchers will still verify ancestry, so step up a level
              } else if (compiled) {
                context = context.parentNode;
              }

              selector = selector.slice(tokens.shift().value.length);
            } // Fetch a seed set for right-to-left matching


            i = matchExpr["needsContext"].test(selector) ? 0 : tokens.length;

            while (i--) {
              token = tokens[i]; // Abort if we hit a combinator

              if (Expr.relative[type = token.type]) {
                break;
              }

              if (find = Expr.find[type]) {
                // Search, expanding context for leading sibling combinators
                if (seed = find(token.matches[0].replace(runescape, funescape), rsibling.test(tokens[0].type) && testContext(context.parentNode) || context)) {
                  // If seed is empty or no tokens remain, we can return early
                  tokens.splice(i, 1);
                  selector = seed.length && toSelector(tokens);

                  if (!selector) {
                    push.apply(results, seed);
                    return results;
                  }

                  break;
                }
              }
            }
          } // Compile and execute a filtering function if one is not provided
          // Provide `match` to avoid retokenization if we modified the selector above


          (compiled || compile(selector, match))(seed, context, !documentIsHTML, results, !context || rsibling.test(selector) && testContext(context.parentNode) || context);
          return results;
        }; // One-time assignments
        // Sort stability


        support.sortStable = expando.split("").sort(sortOrder).join("") === expando; // Support: Chrome 14-35+
        // Always assume duplicates if they aren't passed to the comparison function

        support.detectDuplicates = !!hasDuplicate; // Initialize against the default document

        setDocument(); // Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
        // Detached nodes confoundingly follow *each other*

        support.sortDetached = assert(function (el) {
          // Should return 1, but returns 4 (following)
          return el.compareDocumentPosition(document.createElement("fieldset")) & 1;
        }); // Support: IE<8
        // Prevent attribute/property "interpolation"
        // https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx

        if (!assert(function (el) {
          el.innerHTML = AGPolicy.createHTML("<a href='#'></a>");
          return el.firstChild.getAttribute("href") === "#";
        })) {
          addHandle("type|href|height|width", function (elem, name, isXML) {
            if (!isXML) {
              return elem.getAttribute(name, name.toLowerCase() === "type" ? 1 : 2);
            }
          });
        } // Support: IE<9
        // Use defaultValue in place of getAttribute("value")


        if (!support.attributes || !assert(function (el) {
          el.innerHTML = AGPolicy.createHTML("<input/>");
          el.firstChild.setAttribute("value", "");
          return el.firstChild.getAttribute("value") === "";
        })) {
          addHandle("value", function (elem, name, isXML) {
            if (!isXML && elem.nodeName.toLowerCase() === "input") {
              return elem.defaultValue;
            }
          });
        } // Support: IE<9
        // Use getAttributeNode to fetch booleans when getAttribute lies


        if (!assert(function (el) {
          return el.getAttribute("disabled") == null;
        })) {
          addHandle(booleans, function (elem, name, isXML) {
            var val;

            if (!isXML) {
              return elem[name] === true ? name.toLowerCase() : (val = elem.getAttributeNode(name)) && val.specified ? val.value : null;
            }
          });
        } // EXPOSE
        // Do not expose Sizzle to the global scope in the case of AdGuard ExtendedCss build


        return Sizzle; // EXPOSE
      }(window); //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    }

    return Sizzle;
  };

  /* jshint ignore:end */

  /**
   * Copyright 2016 Adguard Software Ltd
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  /**
   * Class that extends Sizzle and adds support for "matches-css" pseudo element.
   */

  var StylePropertyMatcher = function (window) {
    var isPhantom = !!window._phantom;
    var useFallback = isPhantom && !!window.getMatchedCSSRules;
    /**
     * Unquotes specified value
     * Webkit-based browsers singlequotes <string> content property values
     * Other browsers doublequotes content property values.
     */

    var removeContentQuotes = function removeContentQuotes(value) {
      if (typeof value === 'string') {
        return value.replace(/^(["'])([\s\S]*)\1$/, '$2');
      }

      return value;
    };

    var getComputedStyle = window.getComputedStyle.bind(window);
    var getMatchedCSSRules = useFallback ? window.getMatchedCSSRules.bind(window) : null;
    /**
     * There is an issue in browsers based on old webkit:
     * getComputedStyle(el, ":before") is empty if element is not visible.
     *
     * To circumvent this issue we use getMatchedCSSRules instead.
     *
     * It appears that getMatchedCSSRules sorts the CSS rules
     * in increasing order of specifities of corresponding selectors.
     * We pick the css rule that is being applied to an element based on this assumption.
     *
     * @param element       DOM node
     * @param pseudoElement Optional pseudoElement name
     * @param propertyName  CSS property name
     */

    var getComputedStylePropertyValue = function getComputedStylePropertyValue(element, pseudoElement, propertyName) {
      var value = '';

      if (useFallback && pseudoElement) {
        var cssRules = getMatchedCSSRules(element, pseudoElement) || [];
        var i = cssRules.length;

        while (i-- > 0 && !value) {
          value = cssRules[i].style.getPropertyValue(propertyName);
        }
      } else {
        var style = getComputedStyle(element, pseudoElement);

        if (style) {
          value = style.getPropertyValue(propertyName); // https://bugs.webkit.org/show_bug.cgi?id=93445

          if (propertyName === 'opacity' && utils.isSafariBrowser) {
            value = (Math.round(parseFloat(value) * 100) / 100).toString();
          }
        }
      }

      if (propertyName === 'content') {
        value = removeContentQuotes(value);
      }

      return value;
    };
    /**
     * Adds url parameter quotes for non-regex pattern
     * @param {string} pattern
     */


    var addUrlQuotes = function addUrlQuotes(pattern) {
      // for regex patterns
      if (pattern[0] === '/' && pattern[pattern.length - 1] === '/' && pattern.indexOf('\\"') < 10) {
        // e.g. /^url\\([a-z]{4}:[a-z]{5}/
        // or /^url\\(data\\:\\image\\/gif;base64.+/
        var re = /(\^)?url(\\)?\\\((\w|\[\w)/g;
        return pattern.replace(re, '$1url$2\\\(\\"?$3');
      } // for non-regex patterns


      if (pattern.indexOf('url("') === -1) {
        var _re = /url\((.*?)\)/g;
        return pattern.replace(_re, 'url("$1")');
      }

      return pattern;
    };
    /**
     * Class that matches element style against the specified expression
     * @member {string} propertyName
     * @member {string} pseudoElement
     * @member {RegExp} regex
     */


    var Matcher = function Matcher(propertyFilter, pseudoElement) {
      this.pseudoElement = pseudoElement;

      try {
        var index = propertyFilter.indexOf(':');
        this.propertyName = propertyFilter.substring(0, index).trim();
        var pattern = propertyFilter.substring(index + 1).trim();
        pattern = addUrlQuotes(pattern); // Unescaping pattern
        // For non-regex patterns, (,),[,] should be unescaped, because we require escaping them in filter rules.
        // For regex patterns, ",\ should be escaped, because we manually escape those in extended-css-selector.js.

        if (/^\/.*\/$/.test(pattern)) {
          pattern = pattern.slice(1, -1);
          this.regex = utils.pseudoArgToRegex(pattern);
        } else {
          pattern = pattern.replace(/\\([\\()[\]"])/g, '$1');
          this.regex = utils.createURLRegex(pattern);
        }
      } catch (ex) {
        utils.logError("StylePropertyMatcher: invalid match string ".concat(propertyFilter));
      }
    };
    /**
     * Function to check if element CSS property matches filter pattern
     * @param {Element} element to check
     */


    Matcher.prototype.matches = function (element) {
      if (!this.regex || !this.propertyName) {
        return false;
      }

      var value = getComputedStylePropertyValue(element, this.pseudoElement, this.propertyName);
      return value && this.regex.test(value);
    };
    /**
     * Creates a new pseudo-class and registers it in Sizzle
     */


    var extendSizzle = function extendSizzle(sizzle) {
      // First of all we should prepare Sizzle engine
      sizzle.selectors.pseudos['matches-css'] = sizzle.selectors.createPseudo(function (propertyFilter) {
        var matcher = new Matcher(propertyFilter);
        return function (element) {
          return matcher.matches(element);
        };
      });
      sizzle.selectors.pseudos['matches-css-before'] = sizzle.selectors.createPseudo(function (propertyFilter) {
        var matcher = new Matcher(propertyFilter, ':before');
        return function (element) {
          return matcher.matches(element);
        };
      });
      sizzle.selectors.pseudos['matches-css-after'] = sizzle.selectors.createPseudo(function (propertyFilter) {
        var matcher = new Matcher(propertyFilter, ':after');
        return function (element) {
          return matcher.matches(element);
        };
      });
    }; // EXPOSE


    return {
      extendSizzle: extendSizzle
    };
  }(window);

  /**
   * Copyright 2016 Adguard Software Ltd
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  var matcherUtils = {};
  matcherUtils.MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
  /**
   * Parses argument of matcher pseudo (for matches-attr and matches-property)
   * @param {string} matcherFilter argument of pseudo class
   * @returns {Array}
   */

  matcherUtils.parseMatcherFilter = function (matcherFilter) {
    var FULL_MATCH_MARKER = '"="';
    var rawArgs = [];

    if (matcherFilter.indexOf(FULL_MATCH_MARKER) === -1) {
      // if there is only one pseudo arg
      // e.g. :matches-attr("data-name") or :matches-property("inner.prop")
      // Sizzle will parse it and get rid of quotes
      // so it might be valid arg already without them
      rawArgs.push(matcherFilter);
    } else {
      matcherFilter.split('=').forEach(function (arg) {
        if (arg[0] === '"' && arg[arg.length - 1] === '"') {
          rawArgs.push(arg.slice(1, -1));
        }
      });
    }

    return rawArgs;
  };
  /**
   * @typedef {Object} ArgData
   * @property {string} arg
   * @property {boolean} isRegexp
   */

  /**
   * Parses raw matcher arg
   * @param {string} rawArg
   * @returns {ArgData}
   */


  matcherUtils.parseRawMatcherArg = function (rawArg) {
    var arg = rawArg;
    var isRegexp = !!rawArg && rawArg[0] === '/' && rawArg[rawArg.length - 1] === '/';

    if (isRegexp) {
      // to avoid at least such case — :matches-property("//")
      if (rawArg.length > 2) {
        arg = utils.toRegExp(rawArg);
      } else {
        throw new Error("Invalid regexp: ".concat(rawArg));
      }
    }

    return {
      arg: arg,
      isRegexp: isRegexp
    };
  };
  /**
   * @typedef Chain
   * @property {Object} base
   * @property {string} prop
   * @property {string} value
   */

  /**
   * Checks if the property exists in the base object (recursively).
   * @param {Object} base
   * @param {ArgData[]} chain array of objects - parsed string property chain
   * @param {Array} [output=[]] result acc
   * @returns {Chain[]} array of objects
   */


  matcherUtils.filterRootsByRegexpChain = function (base, chain) {
    var output = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
    var tempProp = chain[0];

    if (chain.length === 1) {
      // eslint-disable-next-line no-restricted-syntax
      for (var key in base) {
        if (tempProp.isRegexp) {
          if (tempProp.arg.test(key)) {
            output.push({
              base: base,
              prop: key,
              value: base[key]
            });
          }
        } else if (tempProp.arg === key) {
          output.push({
            base: base,
            prop: tempProp.arg,
            value: base[key]
          });
        }
      }

      return output;
    } // if there is a regexp prop in input chain
    // e.g. 'unit./^ad.+/.src' for 'unit.ad-1gf2.src unit.ad-fgd34.src'),
    // every base keys should be tested by regexp and it can be more that one results


    if (tempProp.isRegexp) {
      var nextProp = chain.slice(1);
      var baseKeys = []; // eslint-disable-next-line no-restricted-syntax

      for (var _key in base) {
        if (tempProp.arg.test(_key)) {
          baseKeys.push(_key);
        }
      }

      baseKeys.forEach(function (key) {
        var item = base[key];
        matcherUtils.filterRootsByRegexpChain(item, nextProp, output);
      });
    } // avoid TypeError while accessing to null-prop's child


    if (base === null) {
      return;
    }

    var nextBase = base[tempProp.arg];
    chain = chain.slice(1);

    if (nextBase !== undefined) {
      matcherUtils.filterRootsByRegexpChain(nextBase, chain, output);
    }

    return output;
  };
  /**
   * Validates parsed args of matches-property pseudo
   * @param {...ArgData} args
   */


  matcherUtils.validatePropMatcherArgs = function () {
    for (var _len = arguments.length, args = new Array(_len), _key2 = 0; _key2 < _len; _key2++) {
      args[_key2] = arguments[_key2];
    }

    for (var i = 0; i < args.length; i += 1) {
      if (args[i].isRegexp) {
        if (!utils.startsWith(args[i].arg.toString(), '/') || !utils.endsWith(args[i].arg.toString(), '/')) {
          return false;
        } // simple arg check if it is not a regexp

      } else if (!/^[\w-]+$/.test(args[i].arg)) {
        return false;
      }
    }

    return true;
  };

  /**
   * Class that extends Sizzle and adds support for "matches-attr" pseudo element.
   */

  var AttributesMatcher = function () {
    /**
     * Class that matches element attributes against the specified expressions
     * @param {ArgData} nameArg - parsed name argument
     * @param {ArgData} valueArg - parsed value argument
     * @param {string} pseudoElement
     * @constructor
     *
     * @member {string|RegExp} attrName
     * @member {boolean} isRegexpName
     * @member {string|RegExp} attrValue
     * @member {boolean} isRegexpValue
     */
    var AttrMatcher = function AttrMatcher(nameArg, valueArg, pseudoElement) {
      this.pseudoElement = pseudoElement;
      this.attrName = nameArg.arg;
      this.isRegexpName = nameArg.isRegexp;
      this.attrValue = valueArg.arg;
      this.isRegexpValue = valueArg.isRegexp;
    };
    /**
     * Function to check if element attributes matches filter pattern
     * @param {Element} element to check
     */


    AttrMatcher.prototype.matches = function (element) {
      var elAttrs = element.attributes;

      if (elAttrs.length === 0 || !this.attrName) {
        return false;
      }

      var i = 0;

      while (i < elAttrs.length) {
        var attr = elAttrs[i];
        var matched = false;
        var attrNameMatched = this.isRegexpName ? this.attrName.test(attr.name) : this.attrName === attr.name;

        if (!this.attrValue) {
          // for :matches-attr("/regex/") or :matches-attr("attr-name")
          matched = attrNameMatched;
        } else {
          var attrValueMatched = this.isRegexpValue ? this.attrValue.test(attr.value) : this.attrValue === attr.value;
          matched = attrNameMatched && attrValueMatched;
        }

        if (matched) {
          return true;
        }

        i += 1;
      }
    };
    /**
     * Creates a new pseudo-class and registers it in Sizzle
     */


    var extendSizzle = function extendSizzle(sizzle) {
      // First of all we should prepare Sizzle engine
      sizzle.selectors.pseudos['matches-attr'] = sizzle.selectors.createPseudo(function (attrFilter) {
        var _matcherUtils$parseMa = matcherUtils.parseMatcherFilter(attrFilter),
            _matcherUtils$parseMa2 = _slicedToArray(_matcherUtils$parseMa, 2),
            rawName = _matcherUtils$parseMa2[0],
            rawValue = _matcherUtils$parseMa2[1];

        var nameArg = matcherUtils.parseRawMatcherArg(rawName);
        var valueArg = matcherUtils.parseRawMatcherArg(rawValue);

        if (!attrFilter || !matcherUtils.validatePropMatcherArgs(nameArg, valueArg)) {
          throw new Error("Invalid argument of :matches-attr pseudo class: ".concat(attrFilter));
        }

        var matcher = new AttrMatcher(nameArg, valueArg);
        return function (element) {
          return matcher.matches(element);
        };
      });
    }; // EXPOSE


    return {
      extendSizzle: extendSizzle
    };
  }();

  /**
   * Parses raw property arg
   * @param {string} input
   * @returns {ArgData[]} array of objects
   */

  var parseRawPropChain = function parseRawPropChain(input) {
    var PROPS_DIVIDER = '.';
    var REGEXP_MARKER = '/';
    var propsArr = [];
    var str = input;

    while (str.length > 0) {
      if (utils.startsWith(str, PROPS_DIVIDER)) {
        // for cases like '.prop.id' and 'nested..test'
        throw new Error("Invalid chain property: ".concat(input));
      }

      if (!utils.startsWith(str, REGEXP_MARKER)) {
        var isRegexp = false;
        var dividerIndex = str.indexOf(PROPS_DIVIDER);

        if (str.indexOf(PROPS_DIVIDER) === -1) {
          // if there is no '.' left in str
          // take the rest of str as prop
          propsArr.push({
            arg: str,
            isRegexp: isRegexp
          });
          return propsArr;
        } // else take prop from str


        var prop = str.slice(0, dividerIndex); // for cases like 'asadf.?+/.test'

        if (prop.indexOf(REGEXP_MARKER) > -1) {
          // prop is '?+/'
          throw new Error("Invalid chain property: ".concat(prop));
        }

        propsArr.push({
          arg: prop,
          isRegexp: isRegexp
        }); // delete prop from str

        str = str.slice(dividerIndex);
      } else {
        // deal with regexp
        var propChunks = [];
        propChunks.push(str.slice(0, 1)); // if str starts with '/', delete it from str and find closing regexp slash.
        // note that chained property name can not include '/' or '.'
        // so there is no checking for escaped characters

        str = str.slice(1);
        var regexEndIndex = str.indexOf(REGEXP_MARKER);

        if (regexEndIndex < 1) {
          // regexp should be at least === '/./'
          // so we should avoid args like '/id' and 'test.//.id'
          throw new Error("Invalid regexp: ".concat(REGEXP_MARKER).concat(str));
        }

        var _isRegexp = true; // take the rest regexp part

        propChunks.push(str.slice(0, regexEndIndex + 1));

        var _prop = utils.toRegExp(propChunks.join(''));

        propsArr.push({
          arg: _prop,
          isRegexp: _isRegexp
        }); // delete prop from str

        str = str.slice(regexEndIndex + 1);
      }

      if (!str) {
        return propsArr;
      } // str should be like '.nextProp' now
      // so 'zx.prop' or '.' is invalid


      if (!utils.startsWith(str, PROPS_DIVIDER) || utils.startsWith(str, PROPS_DIVIDER) && str.length === 1) {
        throw new Error("Invalid chain property: ".concat(input));
      }

      str = str.slice(1);
    }
  };

  var convertTypeFromStr = function convertTypeFromStr(value) {
    var numValue = Number(value);
    var 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;
  };

  var convertTypeIntoStr = function convertTypeIntoStr(value) {
    var output;

    switch (value) {
      case undefined:
        output = 'undefined';
        break;

      case null:
        output = 'null';
        break;

      default:
        output = value.toString();
    }

    return output;
  };
  /**
   * Class that extends Sizzle and adds support for "matches-property" pseudo element.
   */


  var ElementPropertyMatcher = function () {
    /**
     * Class that matches element properties against the specified expressions
     * @param {ArgData[]} propsChainArg - array of parsed props chain objects
     * @param {ArgData} valueArg - parsed value argument
     * @param {string} pseudoElement
     * @constructor
     *
     * @member {Array} chainedProps
     * @member {boolean} isRegexpName
     * @member {string|RegExp} propValue
     * @member {boolean} isRegexpValue
     */
    var PropMatcher = function PropMatcher(propsChainArg, valueArg, pseudoElement) {
      this.pseudoElement = pseudoElement;
      this.chainedProps = propsChainArg;
      this.propValue = valueArg.arg;
      this.isRegexpValue = valueArg.isRegexp;
    };
    /**
     * Function to check if element properties matches filter pattern
     * @param {Element} element to check
     */


    PropMatcher.prototype.matches = function (element) {
      var ownerObjArr = matcherUtils.filterRootsByRegexpChain(element, this.chainedProps);

      if (ownerObjArr.length === 0) {
        return false;
      }

      var matched = true;

      if (this.propValue) {
        for (var i = 0; i < ownerObjArr.length; i += 1) {
          var realValue = ownerObjArr[i].value;

          if (this.isRegexpValue) {
            matched = this.propValue.test(convertTypeIntoStr(realValue));
          } else {
            // handle 'null' and 'undefined' property values set as string
            if (realValue === 'null' || realValue === 'undefined') {
              matched = this.propValue === realValue;
              break;
            }

            matched = convertTypeFromStr(this.propValue) === realValue;
          }

          if (matched) {
            break;
          }
        }
      }

      return matched;
    };
    /**
     * Creates a new pseudo-class and registers it in Sizzle
     */


    var extendSizzle = function extendSizzle(sizzle) {
      // First of all we should prepare Sizzle engine
      sizzle.selectors.pseudos['matches-property'] = sizzle.selectors.createPseudo(function (propertyFilter) {
        if (!propertyFilter) {
          throw new Error('No argument is given for :matches-property pseudo class');
        }

        var _matcherUtils$parseMa = matcherUtils.parseMatcherFilter(propertyFilter),
            _matcherUtils$parseMa2 = _slicedToArray(_matcherUtils$parseMa, 2),
            rawProp = _matcherUtils$parseMa2[0],
            rawValue = _matcherUtils$parseMa2[1]; // chained property name can not include '/' or '.'
        // so regex prop names with such escaped characters are invalid


        if (rawProp.indexOf('\\/') > -1 || rawProp.indexOf('\\.') > -1) {
          throw new Error("Invalid property name: ".concat(rawProp));
        }

        var propsChainArg = parseRawPropChain(rawProp);
        var valueArg = matcherUtils.parseRawMatcherArg(rawValue);
        var propsToValidate = [].concat(_toConsumableArray(propsChainArg), [valueArg]);

        if (!matcherUtils.validatePropMatcherArgs(propsToValidate)) {
          throw new Error("Invalid argument of :matches-property pseudo class: ".concat(propertyFilter));
        }

        var matcher = new PropMatcher(propsChainArg, valueArg);
        return function (element) {
          return matcher.matches(element);
        };
      });
    }; // EXPOSE


    return {
      extendSizzle: extendSizzle
    };
  }();

  /**
   * Copyright 2020 Adguard Software Ltd
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  /**
   * Class that extends Sizzle and adds support for :is() pseudo element.
   */

  var IsAnyMatcher = function () {
    /**
     * Class that matches element by one of the selectors
     * https://developer.mozilla.org/en-US/docs/Web/CSS/:is
     * @param {Array} selectors
     * @param {string} pseudoElement
     * @constructor
     */
    var IsMatcher = function IsMatcher(selectors, pseudoElement) {
      this.selectors = selectors;
      this.pseudoElement = pseudoElement;
    };
    /**
     * Function to check if element can be matched by any passed selector
     * @param {Element} element to check
     */


    IsMatcher.prototype.matches = function (element) {
      var isMatched = !!this.selectors.find(function (selector) {
        var nodes = document.querySelectorAll(selector);
        return Array.from(nodes).find(function (node) {
          return node === element;
        });
      });
      return isMatched;
    };
    /**
     * Creates a new pseudo-class and registers it in Sizzle
     */


    var extendSizzle = function extendSizzle(sizzle) {
      // First of all we should prepare Sizzle engine
      sizzle.selectors.pseudos['is'] = sizzle.selectors.createPseudo(function (input) {
        if (input === '') {
          throw new Error("Invalid argument of :is pseudo-class: ".concat(input));
        }

        var selectors = input.split(',').map(function (s) {
          return s.trim();
        }); // collect valid selectors and log about invalid ones

        var validSelectors = selectors.reduce(function (acc, selector) {
          if (cssUtils.isSimpleSelectorValid(selector)) {
            acc.push(selector);
          } else {
            utils.logInfo("Invalid selector passed to :is() pseudo-class: '".concat(selector, "'"));
          }

          return acc;
        }, []);
        var matcher = new IsMatcher(validSelectors);
        return function (element) {
          return matcher.matches(element);
        };
      });
    };

    return {
      extendSizzle: extendSizzle
    };
  }();

  /**
   * Copyright 2021 Adguard Software Ltd
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  /**
   * Extended selector factory module, for creating extended selector classes.
   *
   * Extended selection capabilities description:
   * https://github.com/AdguardTeam/ExtendedCss/blob/master/README.md
   */

  var ExtendedSelectorFactory = function () {
    // while adding new markers, constants in other AdGuard repos should be corrected
    // AdGuard browser extension : CssFilterRule.SUPPORTED_PSEUDO_CLASSES and CssFilterRule.EXTENDED_CSS_MARKERS
    // tsurlfilter, SafariConverterLib : EXT_CSS_PSEUDO_INDICATORS
    var PSEUDO_EXTENSIONS_MARKERS = [':has', ':contains', ':has-text', ':matches-css', ':-abp-has', ':-abp-has-text', ':if', ':if-not', ':xpath', ':nth-ancestor', ':upward', ':remove', ':matches-attr', ':matches-property', ':-abp-contains', ':is'];
    var initialized = false;
    var Sizzle;
    /**
     * Lazy initialization of the ExtendedSelectorFactory and objects that might be necessary for creating and applying styles.
     * This method extends Sizzle engine that we use under the hood with our custom pseudo-classes.
     */

    function initialize() {
      if (initialized) {
        return;
      }

      initialized = true; // Our version of Sizzle is initialized lazily as well

      Sizzle = initializeSizzle(); // Add :matches-css-*() support

      StylePropertyMatcher.extendSizzle(Sizzle); // Add :matches-attr() support

      AttributesMatcher.extendSizzle(Sizzle); // Add :matches-property() support

      ElementPropertyMatcher.extendSizzle(Sizzle); // Add :is() support

      IsAnyMatcher.extendSizzle(Sizzle); // Add :contains, :has-text, :-abp-contains support

      var containsPseudo = Sizzle.selectors.createPseudo(function (text) {
        if (/^\s*\/.*\/[gmisuy]*\s*$/.test(text)) {
          text = text.trim();
          var flagsIndex = text.lastIndexOf('/');
          var flags = text.substring(flagsIndex + 1);
          text = text.substr(0, flagsIndex + 1).slice(1, -1).replace(/\\([\\"])/g, '$1');
          var regex;

          try {
            regex = new RegExp(text, flags);
          } catch (e) {
            throw new Error("Invalid argument of :contains pseudo class: ".concat(text));
          }

          return function (elem) {
            var elemTextContent = utils.nodeTextContentGetter.apply(elem);
            return regex.test(elemTextContent);
          };
        }

        text = text.replace(/\\([\\()[\]"])/g, '$1');
        return function (elem) {
          var elemTextContent = utils.nodeTextContentGetter.apply(elem);
          return elemTextContent.indexOf(text) > -1;
        };
      });
      Sizzle.selectors.pseudos['contains'] = containsPseudo;
      Sizzle.selectors.pseudos['has-text'] = containsPseudo;
      Sizzle.selectors.pseudos['-abp-contains'] = containsPseudo; // Add :if, :-abp-has support

      Sizzle.selectors.pseudos['if'] = Sizzle.selectors.pseudos['has'];
      Sizzle.selectors.pseudos['-abp-has'] = Sizzle.selectors.pseudos['has']; // Add :if-not support

      Sizzle.selectors.pseudos['if-not'] = Sizzle.selectors.createPseudo(function (selector) {
        if (typeof selector === 'string') {
          Sizzle.compile(selector);
        }

        return function (elem) {
          return Sizzle(selector, elem).length === 0;
        };
      });
      registerParserOnlyTokens();
    }
    /**
     * Registrate custom tokens for parser.
     * Needed for proper work of pseudos:
     * for checking if the token is last and pseudo-class arguments validation
     */


    function registerParserOnlyTokens() {
      Sizzle.selectors.pseudos['xpath'] = Sizzle.selectors.createPseudo(function (selector) {
        try {
          document.createExpression(selector, null);
        } catch (e) {
          throw new Error("Invalid argument of :xpath pseudo class: ".concat(selector));
        }

        return function () {
          return true;
        };
      });
      Sizzle.selectors.pseudos['nth-ancestor'] = Sizzle.selectors.createPseudo(function (selector) {
        var deep = Number(selector);

        if (Number.isNaN(deep) || deep < 1 || deep >= 256) {
          throw new Error("Invalid argument of :nth-ancestor pseudo class: ".concat(selector));
        }

        return function () {
          return true;
        };
      });
      Sizzle.selectors.pseudos['upward'] = Sizzle.selectors.createPseudo(function (input) {
        if (input === '') {
          throw new Error("Invalid argument of :upward pseudo class: ".concat(input));
        } else if (Number.isInteger(+input) && (+input < 1 || +input >= 256)) {
          throw new Error("Invalid argument of :upward pseudo class: ".concat(input));
        }

        return function () {
          return true;
        };
      });
      Sizzle.selectors.pseudos['remove'] = Sizzle.selectors.createPseudo(function (input) {
        if (input !== '') {
          throw new Error("Invalid argument of :remove pseudo class: ".concat(input));
        }

        return function () {
          return true;
        };
      });
    }
    /**
     * Checks if specified token can be used by document.querySelectorAll.
     */


    function isSimpleToken(token) {
      var type = token.type;

      if (type === 'ID' || type === 'CLASS' || type === 'ATTR' || type === 'TAG' || type === 'CHILD') {
        // known simple tokens
        return true;
      }

      if (type === 'PSEUDO') {
        // check if value contains any of extended pseudo classes
        var i = PSEUDO_EXTENSIONS_MARKERS.length;

        while (i--) {
          if (token.value.indexOf(PSEUDO_EXTENSIONS_MARKERS[i]) >= 0) {
            return false;
          }
        }

        return true;
      } // all others aren't simple


      return false;
    }
    /**
     * Checks if specified token is a combinator
     */


    function isRelationToken(token) {
      var type = token.type;
      return type === ' ' || type === '>' || type === '+' || type === '~';
    }
    /**
     * ExtendedSelectorParser is a helper class for creating various selector instances which
     * all shares a method `querySelectorAll()` and `matches()` implementing different search strategies
     * depending on a type of selector.
     *
     * Currently, there are 3 types:
     *  A trait-less extended selector
     *    - we directly feed selector strings to Sizzle.
     *  A splitted extended selector
     *    - such as #container #feedItem:has(.ads), where it is splitted to `#container` and `#feedItem:has(.ads)`.
     */


    function ExtendedSelectorParser(selectorText, tokens, debug) {
      initialize();

      if (typeof tokens === 'undefined') {
        this.selectorText = cssUtils.normalize(selectorText); // Passing `returnUnsorted` in order to receive tokens in the order that's valid for the browser
        // In Sizzle internally, the tokens are re-sorted: https://github.com/AdguardTeam/ExtendedCss/issues/55

        this.tokens = Sizzle.tokenize(this.selectorText, false, {
          returnUnsorted: true
        });
      } else {
        this.selectorText = selectorText;
        this.tokens = tokens;
      }

      if (debug === true) {
        this.debug = true;
      }
    }

    ExtendedSelectorParser.prototype = {
      /**
       * The main method, creates a selector instance depending on the type of a selector.
       * @public
       */
      createSelector: function createSelector() {
        var debug = this.debug;
        var tokens = this.tokens;
        var selectorText = this.selectorText;

        if (tokens.length !== 1) {
          // Comma-separate selector - can't optimize further
          return new TraitLessSelector(selectorText, debug);
        }

        var xpathPart = this.getXpathPart();

        if (typeof xpathPart !== 'undefined') {
          return new XpathSelector(selectorText, xpathPart, debug);
        }

        var upwardPart = this.getUpwardPart();

        if (typeof upwardPart !== 'undefined') {
          var output;
          var upwardDeep = parseInt(upwardPart, 10); // if upward parameter is not a number, we consider it as a selector

          if (Number.isNaN(upwardDeep)) {
            output = new UpwardSelector(selectorText, upwardPart, debug);
          } else {
            // upward works like nth-ancestor
            var xpath = this.convertNthAncestorToken(upwardDeep);
            output = new XpathSelector(selectorText, xpath, debug);
          }

          return output;
        } // argument of pseudo-class remove;
        // it's defined only if remove is parsed as last token
        // and it's valid only if remove arg is empty string


        var removePart = this.getRemovePart();

        if (typeof removePart !== 'undefined') {
          var hasValidRemovePart = removePart === '';
          return new RemoveSelector(selectorText, hasValidRemovePart, debug);
        }

        tokens = tokens[0];
        var l = tokens.length;
        var lastRelTokenInd = this.getSplitPoint();

        if (typeof lastRelTokenInd === 'undefined') {
          try {
            document.querySelector(selectorText);
          } catch (e) {
            return new TraitLessSelector(selectorText, debug);
          }

          return new NotAnExtendedSelector(selectorText, debug);
        }

        var simple = '';
        var relation = null;
        var complex = '';
        var i = 0;

        for (; i < lastRelTokenInd; i++) {
          // build simple part
          simple += tokens[i].value;
        }

        if (i > 0) {
          // build relation part
          relation = tokens[i++].type;
        } // i is pointing to the start of a complex part.


        for (; i < l; i++) {
          complex += tokens[i].value;
        }

        return lastRelTokenInd === -1 ? new TraitLessSelector(selectorText, debug) : new SplittedSelector(selectorText, simple, relation, complex, debug);
      },

      /**
       * @private
       * @return {number|undefined} An index of a token that is split point.
       * returns undefined if the selector does not contain any complex tokens
       * or it is not eligible for splitting.
       * Otherwise returns an integer indicating the index of the last relation token.
       */
      getSplitPoint: function getSplitPoint() {
        var tokens = this.tokens[0]; // We split selector only when the last compound selector
        // is the only extended selector.

        var latestRelationTokenIndex = -1;
        var haveMetComplexToken = false;

        for (var i = 0, l = tokens.length; i < l; i++) {
          var token = tokens[i];

          if (isRelationToken(token)) {
            if (haveMetComplexToken) {
              return;
            }

            latestRelationTokenIndex = i;
          } else if (!isSimpleToken(token)) {
            haveMetComplexToken = true;
          }
        }

        if (!haveMetComplexToken) {
          return;
        }

        return latestRelationTokenIndex;
      },

      /**
       * @private
       * @return {string|undefined} xpath selector part if exists
       * returns undefined if the selector does not contain xpath tokens
       */
      getXpathPart: function getXpathPart() {
        var tokens = this.tokens[0];

        for (var i = 0, tokensLength = tokens.length; i < tokensLength; i++) {
          var token = tokens[i];

          if (token.type === 'PSEUDO') {
            var matches = token.matches;

            if (matches && matches.length > 1) {
              if (matches[0] === 'xpath') {
                if (this.isLastToken(tokens, i)) {
                  throw new Error('Invalid pseudo: \':xpath\' should be at the end of the selector');
                }

                return matches[1];
              }

              if (matches[0] === 'nth-ancestor') {
                if (this.isLastToken(tokens, i)) {
                  throw new Error('Invalid pseudo: \':nth-ancestor\' should be at the end of the selector');
                }

                var deep = matches[1];

                if (deep > 0 && deep < 256) {
                  return this.convertNthAncestorToken(deep);
                }
              }
            }
          }
        }
      },

      /**
       * converts nth-ancestor/upward deep value to xpath equivalent
       * @param {number} deep
       * @return {string}
       */
      convertNthAncestorToken: function convertNthAncestorToken(deep) {
        var result = '..';

        while (deep > 1) {
          result += '/..';
          deep--;
        }

        return result;
      },

      /**
       * Checks if the token is last,
       * except of remove pseudo-class
       * @param {Array} tokens
       * @param {number} i index of token
       * @returns {boolean}
       */
      isLastToken: function isLastToken(tokens, i) {
        // check id the next parsed token is remove pseudo
        var isNextRemoveToken = tokens[i + 1] && tokens[i + 1].type === 'PSEUDO' && tokens[i + 1].matches && tokens[i + 1].matches[0] === 'remove'; // check if the token is last
        // and if it is not check if it is remove one
        // which should be skipped

        return i + 1 !== tokens.length && !isNextRemoveToken;
      },

      /**
       * @private
       * @return {string|undefined} upward parameter
       * or undefined if the input does not contain upward tokens
       */
      getUpwardPart: function getUpwardPart() {
        var tokens = this.tokens[0];

        for (var i = 0, tokensLength = tokens.length; i < tokensLength; i++) {
          var token = tokens[i];

          if (token.type === 'PSEUDO') {
            var matches = token.matches;

            if (matches && matches.length > 1) {
              if (matches[0] === 'upward') {
                if (this.isLastToken(tokens, i)) {
                  throw new Error('Invalid pseudo: \':upward\' should be at the end of the selector');
                }

                return matches[1];
              }
            }
          }
        }
      },

      /**
       * @private
       * @return {string|undefined} remove parameter
       * or undefined if the input does not contain remove tokens
       */
      getRemovePart: function getRemovePart() {
        var tokens = this.tokens[0];

        for (var i = 0, tokensLength = tokens.length; i < tokensLength; i++) {
          var token = tokens[i];

          if (token.type === 'PSEUDO') {
            var matches = token.matches;

            if (matches && matches.length > 1) {
              if (matches[0] === 'remove') {
                if (i + 1 !== tokensLength) {
                  throw new Error('Invalid pseudo: \':remove\' should be at the end of the selector');
                }

                return matches[1];
              }
            }
          }
        }
      }
    };
    var globalDebuggingFlag = false;

    function isDebugging() {
      return globalDebuggingFlag || this.debug;
    }
    /**
     * This class represents a selector which is not an extended selector.
     * @param {string} selectorText
     * @param {boolean=} debug
     * @final
     */


    function NotAnExtendedSelector(selectorText, debug) {
      this.selectorText = selectorText;
      this.debug = debug;
    }

    NotAnExtendedSelector.prototype = {
      querySelectorAll: function querySelectorAll() {
        return document.querySelectorAll(this.selectorText);
      },
      matches: function matches(element) {
        return element[utils.matchesPropertyName](this.selectorText);
      },
      isDebugging: isDebugging
    };
    /**
     * A trait-less extended selector class.
     * @param {string} selectorText
     * @param {boolean=} debug
     * @constructor
     */

    function TraitLessSelector(selectorText, debug) {
      this.selectorText = selectorText;
      this.debug = debug;
      Sizzle.compile(selectorText);
    }

    TraitLessSelector.prototype = {
      querySelectorAll: function querySelectorAll() {
        return Sizzle(this.selectorText);
      },

      /** @final */
      matches: function matches(element) {
        return Sizzle.matchesSelector(element, this.selectorText);
      },

      /** @final */
      isDebugging: isDebugging
    };
    /**
     * Parental class for such pseudo-classes as xpath, upward, remove
     * which are limited to be the last one token in selector
     *
     * @param {string} selectorText
     * @param {string} pseudoClassArg pseudo-class arg
     * @param {boolean=} debug
     * @constructor
     */

    function BaseLastArgumentSelector(selectorText, pseudoClassArg, debug) {
      this.selectorText = selectorText;
      this.pseudoClassArg = pseudoClassArg;
      this.debug = debug;
      Sizzle.compile(this.selectorText);
    }

    BaseLastArgumentSelector.prototype = {
      querySelectorAll: function querySelectorAll() {
        var _this = this;

        var resultNodes = [];
        var simpleNodes;

        if (this.selectorText) {
          simpleNodes = Sizzle(this.selectorText);

          if (!simpleNodes || !simpleNodes.length) {
            return resultNodes;
          }
        } else {
          simpleNodes = [document];
        }

        simpleNodes.forEach(function (node) {
          _this.searchResultNodes(node, _this.pseudoClassArg, resultNodes);
        });
        return Sizzle.uniqueSort(resultNodes);
      },

      /** @final */
      matches: function matches(element) {
        var results = this.querySelectorAll();
        return results.indexOf(element) > -1;
      },

      /** @final */
      isDebugging: isDebugging,

      /**
       * Primitive method that returns all nodes if pseudo-class arg is defined.
       * That logic works for remove pseudo-class,
       * but for others it should be overridden.
       * @param {Object} node context element
       * @param {string} pseudoClassArg pseudo-class argument
       * @param {Array} result
       */
      searchResultNodes: function searchResultNodes(node, pseudoClassArg, result) {
        if (pseudoClassArg) {
          result.push(node);
        }
      }
    };
    /**
     * Xpath selector class
     * Limited to support 'xpath' to be only the last one token in selector
     * @param {string} selectorText
     * @param {string} xpath value
     * @param {boolean=} debug
     * @constructor
     * @augments BaseLastArgumentSelector
     */

    function XpathSelector(selectorText, xpath, debug) {
      var NO_SELECTOR_MARKER = ':xpath(//';
      var BODY_SELECTOR_REPLACER = 'body:xpath(//';
      var modifiedSelectorText = selectorText; // Normally, a pseudo-class is applied to nodes selected by a selector -- selector:xpath(...).
      // However, :xpath is special as the selector can be ommited.
      // For any other pseudo-class that would mean "apply to ALL DOM nodes",
      // but in case of :xpath it just means "apply me to the document".

      if (utils.startsWith(selectorText, NO_SELECTOR_MARKER)) {
        modifiedSelectorText = selectorText.replace(NO_SELECTOR_MARKER, BODY_SELECTOR_REPLACER);
      }

      BaseLastArgumentSelector.call(this, modifiedSelectorText, xpath, debug);
    }

    XpathSelector.prototype = Object.create(BaseLastArgumentSelector.prototype);
    XpathSelector.prototype.constructor = XpathSelector;
    /**
     * Applies xpath pseudo-class to provided context node
     * @param {Object} node context element
     * @param {string} pseudoClassArg xpath
     * @param {Array} result
     * @override
     */

    XpathSelector.prototype.searchResultNodes = function (node, pseudoClassArg, result) {
      var xpathResult = document.evaluate(pseudoClassArg, node, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null);
      var iNode; // eslint-disable-next-line no-cond-assign

      while (iNode = xpathResult.iterateNext()) {
        result.push(iNode);
      }
    };
    /**
     * Upward selector class
     * Limited to support 'upward' to be only the last one token in selector
     * @param {string} selectorText
     * @param {string} upwardSelector value
     * @param {boolean=} debug
     * @constructor
     * @augments BaseLastArgumentSelector
     */


    function UpwardSelector(selectorText, upwardSelector, debug) {
      BaseLastArgumentSelector.call(this, selectorText, upwardSelector, debug);
    }

    UpwardSelector.prototype = Object.create(BaseLastArgumentSelector.prototype);
    UpwardSelector.prototype.constructor = UpwardSelector;
    /**
     * Applies upward pseudo-class to provided context node
     * @param {Object} node context element
     * @param {string} upwardSelector upward selector
     * @param {Array} result
     * @override
     */

    UpwardSelector.prototype.searchResultNodes = function (node, upwardSelector, result) {
      if (upwardSelector !== '') {
        var parent = node.parentElement;

        if (parent === null) {
          return;
        }

        node = parent.closest(upwardSelector);

        if (node === null) {
          return;
        }
      }

      result.push(node);
    };
    /**
     * Remove selector class
     * Limited to support 'remove' to be only the last one token in selector
     * @param {string} selectorText
     * @param {boolean} hasValidRemovePart
     * @param {boolean=} debug
     * @constructor
     * @augments BaseLastArgumentSelector
     */


    function RemoveSelector(selectorText, hasValidRemovePart, debug) {
      var REMOVE_PSEUDO_MARKER = ':remove()';
      var removeMarkerIndex = selectorText.indexOf(REMOVE_PSEUDO_MARKER); // deleting remove part of rule instead of which
      // pseudo-property property 'remove' will be added by ExtendedCssParser

      var modifiedSelectorText = selectorText.slice(0, removeMarkerIndex);
      BaseLastArgumentSelector.call(this, modifiedSelectorText, hasValidRemovePart, debug); // mark extendedSelector as Remove one for ExtendedCssParser

      this.isRemoveSelector = true;
    }

    RemoveSelector.prototype = Object.create(BaseLastArgumentSelector.prototype);
    RemoveSelector.prototype.constructor = RemoveSelector;
    /**
     * A splitted extended selector class.
     *
     * #container #feedItem:has(.ads)
     * +--------+                     simple
     *           +                    relation
     *            +-----------------+ complex
     * We split selector only when the last selector is complex
     * @param {string} selectorText
     * @param {string} simple
     * @param {string} relation
     * @param {string} complex
     * @param {boolean=} debug
     * @constructor
     * @extends TraitLessSelector
     */

    function SplittedSelector(selectorText, simple, relation, complex, debug) {
      TraitLessSelector.call(this, selectorText, debug);
      this.simple = simple;
      this.relation = relation;
      this.complex = complex;
      Sizzle.compile(complex);
    }

    SplittedSelector.prototype = Object.create(TraitLessSelector.prototype);
    SplittedSelector.prototype.constructor = SplittedSelector;
    /** @override */

    SplittedSelector.prototype.querySelectorAll = function () {
      var _this2 = this;

      var resultNodes = [];
      var simpleNodes;
      var simple = this.simple;
      var relation;

      if (simple) {
        // First we use simple selector to narrow our search
        simpleNodes = document.querySelectorAll(simple);

        if (!simpleNodes || !simpleNodes.length) {
          return resultNodes;
        }

        relation = this.relation;
      } else {
        simpleNodes = [document];
        relation = ' ';
      }

      switch (relation) {
        case ' ':
          simpleNodes.forEach(function (node) {
            _this2.relativeSearch(node, resultNodes);
          });
          break;

        case '>':
          {
            simpleNodes.forEach(function (node) {
              Object.values(node.children).forEach(function (childNode) {
                if (_this2.matches(childNode)) {
                  resultNodes.push(childNode);
                }
              });
            });
            break;
          }

        case '+':
          {
            simpleNodes.forEach(function (node) {
              var parentNode = node.parentNode;
              Object.values(parentNode.children).forEach(function (childNode) {
                if (_this2.matches(childNode) && childNode.previousElementSibling === node) {
                  resultNodes.push(childNode);
                }
              });
            });
            break;
          }

        case '~':
          {
            simpleNodes.forEach(function (node) {
              var parentNode = node.parentNode;
              Object.values(parentNode.children).forEach(function (childNode) {
                if (_this2.matches(childNode) && node.compareDocumentPosition(childNode) === 4) {
                  resultNodes.push(childNode);
                }
              });
            });
            break;
          }
      }

      return Sizzle.uniqueSort(resultNodes);
    };
    /**
     * Performs a search of "complex" part relative to results for the "simple" part.
     * @param {Node} node a node matching the "simple" part.
     * @param {Node[]} result an array to append search result.
     */


    SplittedSelector.prototype.relativeSearch = function (node, results) {
      Sizzle(this.complex, node, results);
    };

    return {
      /**
       * Wraps the inner class so that the instance is not exposed.
       */
      createSelector: function createSelector(selector, tokens, debug) {
        return new ExtendedSelectorParser(selector, tokens, debug).createSelector();
      },

      /**
       * Mark every selector as a selector being debugged, so that timing information
       * for the selector is printed to the console.
       */
      enableGlobalDebugging: function enableGlobalDebugging() {
        globalDebuggingFlag = true;
      }
    };
  }();

  /**
   * Copyright 2016 Adguard Software Ltd
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  /**
   * A helper class that parses stylesheets containing extended selectors
   * into ExtendedSelector instances and key-value maps of style declarations.
   * Please note, that it does not support any complex things like media queries and such.
   */

  var ExtendedCssParser = function () {
    var reDeclEnd = /[;}]/g;
    var reDeclDivider = /[;:}]/g;
    var reNonWhitespace = /\S/g;
    var Sizzle;
    /**
     * @param {string} cssText
     * @constructor
     */

    function Parser(cssText) {
      this.cssText = cssText;
    }

    Parser.prototype = {
      error: function error(position) {
        throw new Error("CssParser: parse error at position ".concat(this.posOffset + position));
      },

      /**
       * Validates that the tokens correspond to a valid selector.
       * Sizzle is different from browsers and some selectors that it tolerates aren't actually valid.
       * For instance, "div >" won't work in a browser, but it will in Sizzle (it'd be the same as "div > *").
       *
       * @param {*} selectors An array of SelectorData (selector, groups)
       * @returns {boolean} false if any of the groups are invalid
       */
      validateSelectors: function validateSelectors(selectors) {
        var iSelectors = selectors.length;

        while (iSelectors--) {
          var groups = selectors[iSelectors].groups;
          var iGroups = groups.length;

          while (iGroups--) {
            var tokens = groups[iGroups];
            var lastToken = tokens[tokens.length - 1];

            if (Sizzle.selectors.relative[lastToken.type]) {
              return false;
            }
          }
        }

        return true;
      },

      /**
       * Parses a stylesheet and returns a list of pairs of an ExtendedSelector and a styles map.
       * This method will throw an error in case of an obviously invalid input.
       * If any of the selectors used in the stylesheet cannot be compiled into an ExtendedSelector,
       * it will be ignored.
       *
       * @typedef {Object} ExtendedStyle
       * @property {Object} selector An instance of the {@link ExtendedSelector} class
       * @property {Object} styleMap A map of styles parsed
       *
       * @returns {Array.<ExtendedStyle>} An array of the styles parsed
       */
      parseCss: function parseCss() {
        this.posOffset = 0;

        if (!this.cssText) {
          this.error(0);
        }

        var results = [];

        while (this.cssText) {
          // Apply tolerant tokenization.
          var parseResult = Sizzle.tokenize(this.cssText, false, {
            tolerant: true,
            returnUnsorted: true
          });
          var selectorData = parseResult.selectors;
          this.nextIndex = parseResult.nextIndex;

          if (this.cssText.charCodeAt(this.nextIndex) !== 123 ||
          /* charCode of '{' */
          !this.validateSelectors(selectorData)) {
            this.error(this.nextIndex);
          }

          this.nextIndex++; // Move the pointer to the start of style declaration.

          var styleMap = this.parseNextStyle();
          var debug = false; // If there is a style property 'debug', mark the selector
          // as a debuggable selector, and delete the style declaration.

          var debugPropertyValue = styleMap['debug'];

          if (typeof debugPropertyValue !== 'undefined') {
            if (debugPropertyValue === 'global') {
              ExtendedSelectorFactory.enableGlobalDebugging();
            }

            debug = true;
            delete styleMap['debug'];
          } // Creating an ExtendedSelector instance for every selector we got from Sizzle.tokenize.
          // This is quite important as Sizzle does a poor job at executing selectors like "selector1, selector2".


          for (var i = 0, l = selectorData.length; i < l; i++) {
            var data = selectorData[i];

            try {
              var extendedSelector = ExtendedSelectorFactory.createSelector(data.selectorText, data.groups, debug);

              if (extendedSelector.pseudoClassArg && extendedSelector.isRemoveSelector) {
                // if there is remove pseudo-class in rule,
                // the element will be removed and no other styles will be applied
                styleMap['remove'] = 'true';
              }

              results.push({
                selector: extendedSelector,
                style: styleMap
              });
            } catch (ex) {
              utils.logError("ExtendedCssParser: ignoring invalid selector ".concat(data.selectorText));
            }
          }
        }

        return results;
      },
      parseNextStyle: function parseNextStyle() {
        var styleMap = Object.create(null);
        var bracketPos = this.parseUntilClosingBracket(styleMap); // Cut out matched portion from cssText.

        reNonWhitespace.lastIndex = bracketPos + 1;
        var match = reNonWhitespace.exec(this.cssText);

        if (match === null) {
          this.cssText = '';
          return styleMap;
        }

        var matchPos = match.index;
        this.cssText = this.cssText.slice(matchPos);
        this.posOffset += matchPos;
        return styleMap;
      },

      /**
       * @return {number} an index of the next '}' in `this.cssText`.
       */
      parseUntilClosingBracket: function parseUntilClosingBracket(styleMap) {
        // Expects ":", ";", and "}".
        reDeclDivider.lastIndex = this.nextIndex;
        var match = reDeclDivider.exec(this.cssText);

        if (match === null) {
          this.error(this.nextIndex);
        }

        var matchPos = match.index;
        var matched = match[0];

        if (matched === '}') {
          return matchPos;
        }

        if (matched === ':') {
          var colonIndex = matchPos; // Expects ";" and "}".

          reDeclEnd.lastIndex = colonIndex;
          match = reDeclEnd.exec(this.cssText);

          if (match === null) {
            this.error(colonIndex);
          }

          matchPos = match.index;
          matched = match[0]; // Populates the `styleMap` key-value map.

          var property = this.cssText.slice(this.nextIndex, colonIndex).trim();
          var value = this.cssText.slice(colonIndex + 1, matchPos).trim();
          styleMap[property] = value; // If found "}", re-run the outer loop.

          if (matched === '}') {
            return matchPos;
          }
        } // matchPos is the position of the next ';'.
        // Increase 'nextIndex' and re-run the loop.


        this.nextIndex = matchPos + 1;
        return this.parseUntilClosingBracket(styleMap); // Should be a subject of tail-call optimization.
      }
    };
    return {
      parseCss: function parseCss(cssText) {
        Sizzle = initializeSizzle();
        return new Parser(cssUtils.normalize(cssText)).parseCss();
      }
    };
  }();

  /**
   * This callback is used to get affected node elements and handle style properties
   * before they are applied to them if it is necessary
   * @callback beforeStyleApplied
   * @param {object} affectedElement - Object containing DOM node and rule to be applied
   * @return {object} affectedElement - Same or modified object containing DOM node and rule to be applied
   */

  /**
   * Extended css class
   *
   * @param {Object} configuration
   * @param {string} configuration.styleSheet - the CSS stylesheet text
   * @param {beforeStyleApplied} [configuration.beforeStyleApplied] - the callback that handles affected elements
   * @constructor
   */

  function ExtendedCss(configuration) {
    if (!configuration) {
      throw new Error('Configuration is not provided.');
    }

    var styleSheet = configuration.styleSheet;
    var beforeStyleApplied = configuration.beforeStyleApplied;

    if (beforeStyleApplied && typeof beforeStyleApplied !== 'function') {
      // eslint-disable-next-line max-len
      throw new Error("Wrong configuration. Type of 'beforeStyleApplied' field should be a function, received: ".concat(_typeof(beforeStyleApplied)));
    } // We use EventTracker to track the event that is likely to cause the mutation.
    // The problem is that we cannot use `window.event` directly from the mutation observer call
    // as we're not in the event handler context anymore.


    var EventTracker = function () {
      var ignoredEventTypes = ['mouseover', 'mouseleave', 'mouseenter', 'mouseout'];
      var LAST_EVENT_TIMEOUT_MS = 10;
      var EVENTS = [// keyboard events
      'keydown', 'keypress', 'keyup', // mouse events
      'auxclick', 'click', 'contextmenu', 'dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'mouseover', 'mouseout', 'mouseup', 'pointerlockchange', 'pointerlockerror', 'select', 'wheel']; // 'wheel' event makes scrolling in Safari twitchy
      // https://github.com/AdguardTeam/ExtendedCss/issues/120

      var safariProblematicEvents = ['wheel'];
      var trackedEvents = utils.isSafariBrowser ? EVENTS.filter(function (el) {
        return !(safariProblematicEvents.indexOf(el) > -1);
      }) : EVENTS;
      var lastEventType;
      var lastEventTime;

      var trackEvent = function trackEvent(e) {
        lastEventType = e.type;
        lastEventTime = Date.now();
      };

      trackedEvents.forEach(function (evName) {
        document.documentElement.addEventListener(evName, trackEvent, true);
      });

      var getLastEventType = function getLastEventType() {
        return lastEventType;
      };

      var getTimeSinceLastEvent = function getTimeSinceLastEvent() {
        return Date.now() - lastEventTime;
      };

      return {
        isIgnoredEventType: function isIgnoredEventType() {
          return ignoredEventTypes.indexOf(getLastEventType()) > -1 && getTimeSinceLastEvent() < LAST_EVENT_TIMEOUT_MS;
        }
      };
    }();

    var rules = [];
    var affectedElements = [];
    var removalsStatistic = {};
    var domObserved;
    var eventListenerSupported = window.addEventListener;
    var domMutationObserver;

    function observeDocument(callback) {
      // We are trying to limit the number of callback calls by not calling it on all kind of "hover" events.
      // The rationale behind this is that "hover" events often cause attributes modification,
      // but re-applying extCSS rules will be useless as these attribute changes are usually transient.
      var isIgnoredMutation = function isIgnoredMutation(mutations) {
        for (var i = 0; i < mutations.length; i += 1) {
          if (mutations.type !== 'attributes') {
            return false;
          }
        }

        return true;
      };

      if (utils.MutationObserver) {
        domMutationObserver = new utils.MutationObserver(function (mutations) {
          if (!mutations || mutations.length === 0) {
            return;
          }

          if (EventTracker.isIgnoredEventType() && isIgnoredMutation(mutations)) {
            return;
          }

          callback();
        });
        domMutationObserver.observe(document, {
          childList: true,
          subtree: true,
          attributes: true,
          attributeFilter: ['id', 'class']
        });
      } else if (eventListenerSupported) {
        document.addEventListener('DOMNodeInserted', callback, false);
        document.addEventListener('DOMNodeRemoved', callback, false);
        document.addEventListener('DOMAttrModified', callback, false);
      }
    }

    function disconnectDocument(callback) {
      if (domMutationObserver) {
        domMutationObserver.disconnect();
      } else if (eventListenerSupported) {
        document.removeEventListener('DOMNodeInserted', callback, false);
        document.removeEventListener('DOMNodeRemoved', callback, false);
        document.removeEventListener('DOMAttrModified', callback, false);
      }
    }

    var MAX_STYLE_PROTECTION_COUNT = 50;
    var protectionObserverOption = {
      attributes: true,
      attributeOldValue: true,
      attributeFilter: ['style']
    };
    /**
     * Creates MutationObserver protection function
     *
     * @param styles
     * @return {protectionFunction}
     */

    function createProtectionFunction(styles) {
      function protectionFunction(mutations, observer) {
        if (!mutations.length) {
          return;
        }

        var mutation = mutations[0];
        var target = mutation.target;
        observer.disconnect();
        styles.forEach(function (style) {
          setStyleToElement(target, style);
        });

        if (++observer.styleProtectionCount < MAX_STYLE_PROTECTION_COUNT) {
          observer.observe(target, protectionObserverOption);
        } else {
          utils.logError('ExtendedCss: infinite loop protection for style');
        }
      }

      return protectionFunction;
    }
    /**
     * Sets up a MutationObserver which protects style attributes from changes
     * @param node DOM node
     * @param rules rules
     * @returns Mutation observer used to protect attribute or null if there's nothing to protect
     */


    function protectStyleAttribute(node, rules) {
      if (!utils.MutationObserver) {
        return null;
      }

      var styles = rules.map(function (r) {
        return r.style;
      });
      var protectionObserver = new utils.MutationObserver(createProtectionFunction(styles));
      protectionObserver.observe(node, protectionObserverOption); // Adds an expando to the observer to keep 'style fix counts'.

      protectionObserver.styleProtectionCount = 0;
      return protectionObserver;
    }

    function removeSuffix(str, suffix) {
      var index = str.indexOf(suffix, str.length - suffix.length);

      if (index >= 0) {
        return str.substring(0, index);
      }

      return str;
    }
    /**
     * Finds affectedElement object for the specified DOM node
     * @param node  DOM node
     * @returns     affectedElement found or null
     */


    function findAffectedElement(node) {
      for (var i = 0; i < affectedElements.length; i += 1) {
        if (affectedElements[i].node === node) {
          return affectedElements[i];
        }
      }

      return null;
    }

    function removeElement(affectedElement) {
      var node = affectedElement.node;
      affectedElement.removed = true;
      var elementSelector = utils.getNodeSelector(node); // check if the element has been already removed earlier

      var elementRemovalsCounter = removalsStatistic[elementSelector] || 0; // if removals attempts happened more than specified we do not try to remove node again

      if (elementRemovalsCounter > MAX_STYLE_PROTECTION_COUNT) {
        utils.logError('ExtendedCss: infinite loop protection for SELECTOR', elementSelector);
        return;
      }

      if (node.parentNode) {
        node.parentNode.removeChild(node);
        removalsStatistic[elementSelector] = elementRemovalsCounter + 1;
      }
    }
    /**
     * Applies style to the specified DOM node
     * @param affectedElement Object containing DOM node and rule to be applied
     */


    function applyStyle(affectedElement) {
      if (affectedElement.protectionObserver) {
        // Style is already applied and protected by the observer
        return;
      }

      if (beforeStyleApplied) {
        affectedElement = beforeStyleApplied(affectedElement);

        if (!affectedElement) {
          return;
        }
      }

      var _affectedElement = affectedElement,
          node = _affectedElement.node;

      for (var i = 0; i < affectedElement.rules.length; i++) {
        var style = affectedElement.rules[i].style;

        if (style['remove'] === 'true') {
          removeElement(affectedElement);
          return;
        }

        setStyleToElement(node, style);
      }
    }
    /**
     * Sets style to the specified DOM node
     * @param node element
     * @param style style
     */


    function setStyleToElement(node, style) {
      Object.keys(style).forEach(function (prop) {
        // Apply this style only to existing properties
        // We can't use hasOwnProperty here (does not work in FF)
        if (typeof node.style.getPropertyValue(prop) !== 'undefined') {
          var value = style[prop]; // First we should remove !important attribute (or it won't be applied')

          value = removeSuffix(value.trim(), '!important').trim();
          node.style.setProperty(prop, value, 'important');
        }
      });
    }
    /**
     * Reverts style for the affected object
     */


    function revertStyle(affectedElement) {
      if (affectedElement.protectionObserver) {
        affectedElement.protectionObserver.disconnect();
      }

      affectedElement.node.style.cssText = affectedElement.originalStyle;
    }
    /**
     * Applies specified rule and returns list of elements affected
     * @param rule Rule to apply
     * @returns List of elements affected by this rule
     */


    function applyRule(rule) {
      var debug = rule.selector.isDebugging();
      var start;

      if (debug) {
        start = utils.AsyncWrapper.now();
      }

      var selector = rule.selector;
      var nodes = selector.querySelectorAll();
      nodes.forEach(function (node) {
        var affectedElement = findAffectedElement(node);

        if (affectedElement) {
          affectedElement.rules.push(rule);
          applyStyle(affectedElement);
        } else {
          // Applying style first time
          var originalStyle = node.style.cssText;
          affectedElement = {
            node: node,
            // affected DOM node
            rules: [rule],
            // rules to be applied
            originalStyle: originalStyle,
            // original node style
            protectionObserver: null // style attribute observer

          };
          applyStyle(affectedElement);
          affectedElements.push(affectedElement);
        }
      });

      if (debug) {
        var elapsed = utils.AsyncWrapper.now() - start;

        if (!('timingStats' in rule)) {
          rule.timingStats = new utils.Stats();
        }

        rule.timingStats.push(elapsed);
      }

      return nodes;
    }
    /**
     * Applies filtering rules
     */


    function applyRules() {
      var elementsIndex = []; // some rules could make call - selector.querySelectorAll() temporarily to change node id attribute
      // this caused MutationObserver to call recursively
      // https://github.com/AdguardTeam/ExtendedCss/issues/81

      stopObserve();
      rules.forEach(function (rule) {
        var nodes = applyRule(rule);
        Array.prototype.push.apply(elementsIndex, nodes);
      }); // Now revert styles for elements which are no more affected

      var l = affectedElements.length; // do nothing if there is no elements to process

      if (elementsIndex.length > 0) {
        while (l--) {
          var obj = affectedElements[l];

          if (elementsIndex.indexOf(obj.node) === -1) {
            // Time to revert style
            revertStyle(obj);
            affectedElements.splice(l, 1);
          } else if (!obj.removed) {
            // Add style protection observer
            // Protect "style" attribute from changes
            if (!obj.protectionObserver) {
              obj.protectionObserver = protectStyleAttribute(obj.node, obj.rules);
            }
          }
        }
      } // After styles are applied we can start observe again


      observe();
      printTimingInfo();
    }

    var APPLY_RULES_DELAY = 150;
    var applyRulesScheduler = new utils.AsyncWrapper(applyRules, APPLY_RULES_DELAY);
    var mainCallback = applyRulesScheduler.run.bind(applyRulesScheduler);

    function observe() {
      if (domObserved) {
        return;
      } // Handle dynamically added elements


      domObserved = true;
      observeDocument(mainCallback);
    }

    function stopObserve() {
      if (!domObserved) {
        return;
      }

      domObserved = false;
      disconnectDocument(mainCallback);
    }

    function apply() {
      applyRules();

      if (document.readyState !== 'complete') {
        document.addEventListener('DOMContentLoaded', applyRules);
      }
    }
    /**
     * Disposes ExtendedCss and removes our styles from matched elements
     */


    function dispose() {
      stopObserve();
      affectedElements.forEach(function (obj) {
        revertStyle(obj);
      });
    }

    var timingsPrinted = false;
    /**
     * Prints timing information for all selectors marked as "debug"
     */

    function printTimingInfo() {
      if (timingsPrinted) {
        return;
      }

      timingsPrinted = true;
      var timings = rules.filter(function (rule) {
        return rule.selector.isDebugging();
      }).map(function (rule) {
        return {
          selectorText: rule.selector.selectorText,
          timingStats: rule.timingStats
        };
      });

      if (timings.length === 0) {
        return;
      } // Add location.href to the message to distinguish frames


      utils.logInfo('[ExtendedCss] Timings for %o:\n%o (in milliseconds)', window.location.href, timings);
    } // First of all parse the stylesheet


    rules = ExtendedCssParser.parseCss(styleSheet); // EXPOSE

    this.dispose = dispose;
    this.apply = apply;
    /** Exposed for testing purposes only */

    this._getAffectedElements = function () {
      return affectedElements;
    };
  }
  /**
   * Expose querySelectorAll for debugging and validating selectors
   *
   * @param {string} selectorText selector text
   * @param {boolean} noTiming if true -- do not print the timing to the console
   * @returns {Array<Node>|NodeList} a list of elements found
   * @throws Will throw an error if the argument is not a valid selector
   */


  ExtendedCss.query = function (selectorText, noTiming) {
    if (typeof selectorText !== 'string') {
      throw new Error('Selector text is empty');
    }

    var now = utils.AsyncWrapper.now;
    var start = now();

    try {
      return ExtendedSelectorFactory.createSelector(selectorText).querySelectorAll();
    } finally {
      var end = now();

      if (!noTiming) {
        utils.logInfo("[ExtendedCss] Elapsed: ".concat(Math.round((end - start) * 1000), " \u03BCs."));
      }
    }
  };

  return ExtendedCss;

}());

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址