您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
一个让用户可以使用扩展 CSS 选择器的库
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @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; }());