extended-css

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

Version vom 30.09.2022. Aktuellste Version

Dieses Skript sollte nicht direkt installiert werden. Es handelt sich hier um eine Bibliothek für andere Skripte, welche über folgenden Befehl in den Metadaten eines Skriptes eingebunden wird // @require https://update.greatest.deepsurf.us/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;

}());