| Index: chrome/content/elemHideEmulation.js |
| diff --git a/chrome/content/elemHideEmulation.js b/chrome/content/elemHideEmulation.js |
| deleted file mode 100644 |
| index 447a479a6fc9e835291506c7a3127b809d17a4de..0000000000000000000000000000000000000000 |
| --- a/chrome/content/elemHideEmulation.js |
| +++ /dev/null |
| @@ -1,514 +0,0 @@ |
| -/* |
| - * This file is part of Adblock Plus <https://adblockplus.org/>, |
| - * Copyright (C) 2006-2017 eyeo GmbH |
| - * |
| - * Adblock Plus is free software: you can redistribute it and/or modify |
| - * it under the terms of the GNU General Public License version 3 as |
| - * published by the Free Software Foundation. |
| - * |
| - * Adblock Plus is distributed in the hope that it will be useful, |
| - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| - * GNU General Public License for more details. |
| - * |
| - * You should have received a copy of the GNU General Public License |
| - * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
| - */ |
| - |
| -/* globals filterToRegExp */ |
| - |
| -"use strict"; |
| - |
| -const MIN_INVOCATION_INTERVAL = 3000; |
| -const abpSelectorRegexp = /:-abp-([\w-]+)\(/i; |
| - |
| -function splitSelector(selector) |
| -{ |
| - if (selector.indexOf(",") == -1) |
| - return [selector]; |
| - |
| - let selectors = []; |
| - let start = 0; |
| - let level = 0; |
| - let sep = ""; |
| - |
| - for (let i = 0; i < selector.length; i++) |
| - { |
| - let chr = selector[i]; |
| - |
| - if (chr == "\\") // ignore escaped characters |
| - i++; |
| - else if (chr == sep) // don't split within quoted text |
| - sep = ""; // e.g. [attr=","] |
| - else if (sep == "") |
| - { |
| - if (chr == '"' || chr == "'") |
| - sep = chr; |
| - else if (chr == "(") // don't split between parentheses |
| - level++; // e.g. :matches(div,span) |
| - else if (chr == ")") |
| - level = Math.max(0, level - 1); |
| - else if (chr == "," && level == 0) |
| - { |
| - selectors.push(selector.substring(start, i)); |
| - start = i + 1; |
| - } |
| - } |
| - } |
| - |
| - selectors.push(selector.substring(start)); |
| - return selectors; |
| -} |
| - |
| -/** Return position of node from parent. |
| - * @param {Node} node the node to find the position of. |
| - * @return {number} One-based index like for :nth-child(), or 0 on error. |
| - */ |
| -function positionInParent(node) |
| -{ |
| - let {children} = node.parentNode; |
| - for (let i = 0; i < children.length; i++) |
| - if (children[i] == node) |
| - return i + 1; |
| - return 0; |
| -} |
| - |
| -function makeSelector(node, selector) |
| -{ |
| - if (!node.parentElement) |
| - { |
| - let newSelector = ":root"; |
| - if (selector) |
| - newSelector += " > " + selector; |
| - return newSelector; |
| - } |
| - let idx = positionInParent(node); |
| - if (idx > 0) |
| - { |
| - let newSelector = `${node.tagName}:nth-child(${idx})`; |
| - if (selector) |
| - newSelector += " > " + selector; |
| - return makeSelector(node.parentElement, newSelector); |
| - } |
| - |
| - return selector; |
| -} |
| - |
| -function parseSelectorContent(content, startIndex) |
| -{ |
| - let parens = 1; |
| - let quote = null; |
| - let i = startIndex; |
| - for (; i < content.length; i++) |
| - { |
| - let c = content[i]; |
| - if (c == "\\") |
| - { |
| - // Ignore escaped characters |
| - i++; |
| - } |
| - else if (quote) |
| - { |
| - if (c == quote) |
| - quote = null; |
| - } |
| - else if (c == "'" || c == '"') |
| - quote = c; |
| - else if (c == "(") |
| - parens++; |
| - else if (c == ")") |
| - { |
| - parens--; |
| - if (parens == 0) |
| - break; |
| - } |
| - } |
| - |
| - if (parens > 0) |
| - return null; |
| - return {text: content.substring(startIndex, i), end: i}; |
| -} |
| - |
| -/** Stringified style objects |
| - * @typedef {Object} StringifiedStyle |
| - * @property {string} style CSS style represented by a string. |
| - * @property {string[]} subSelectors selectors the CSS properties apply to. |
| - */ |
| - |
| -/** |
| - * Produce a string representation of the stylesheet entry. |
| - * @param {CSSStyleRule} rule the CSS style rule. |
| - * @return {StringifiedStyle} the stringified style. |
| - */ |
| -function stringifyStyle(rule) |
| -{ |
| - let styles = []; |
| - for (let i = 0; i < rule.style.length; i++) |
| - { |
| - let property = rule.style.item(i); |
| - let value = rule.style.getPropertyValue(property); |
| - let priority = rule.style.getPropertyPriority(property); |
| - styles.push(`${property}: ${value}${priority ? " !" + priority : ""};`); |
| - } |
| - styles.sort(); |
| - return { |
| - style: styles.join(" "), |
| - subSelectors: splitSelector(rule.selectorText) |
| - }; |
| -} |
| - |
| -function* evaluate(chain, index, prefix, subtree, styles) |
| -{ |
| - if (index >= chain.length) |
| - { |
| - yield prefix; |
| - return; |
| - } |
| - for (let [selector, element] of |
| - chain[index].getSelectors(prefix, subtree, styles)) |
| - yield* evaluate(chain, index + 1, selector, element, styles); |
| -} |
| - |
| -function PlainSelector(selector) |
| -{ |
| - this._selector = selector; |
| -} |
| - |
| -PlainSelector.prototype = { |
| - /** |
| - * Generator function returning a pair of selector |
| - * string and subtree. |
| - * @param {string} prefix the prefix for the selector. |
| - * @param {Node} subtree the subtree we work on. |
| - * @param {StringifiedStyle[]} styles the stringified style objects. |
| - */ |
| - *getSelectors(prefix, subtree, styles) |
| - { |
| - yield [prefix + this._selector, subtree]; |
| - } |
| -}; |
| - |
| -const incompletePrefixRegexp = /[\s>+~]$/; |
| - |
| -function HasSelector(selectors) |
| -{ |
| - this._innerSelectors = selectors; |
| -} |
| - |
| -HasSelector.prototype = { |
| - requiresHiding: true, |
| - |
| - get dependsOnStyles() |
| - { |
| - return this._innerSelectors.some(selector => selector.dependsOnStyles); |
| - }, |
| - |
| - *getSelectors(prefix, subtree, styles) |
| - { |
| - for (let element of this.getElements(prefix, subtree, styles)) |
| - yield [makeSelector(element, ""), element]; |
| - }, |
| - |
| - /** |
| - * Generator function returning selected elements. |
| - * @param {string} prefix the prefix for the selector. |
| - * @param {Node} subtree the subtree we work on. |
| - * @param {StringifiedStyle[]} styles the stringified style objects. |
| - */ |
| - *getElements(prefix, subtree, styles) |
| - { |
| - let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? |
| - prefix + "*" : prefix; |
| - let elements = subtree.querySelectorAll(actualPrefix); |
| - for (let element of elements) |
| - { |
| - let iter = evaluate(this._innerSelectors, 0, "", element, styles); |
| - for (let selector of iter) |
| - if (element.querySelector(selector)) |
| - yield element; |
| - } |
| - } |
| -}; |
| - |
| -function ContainsSelector(textContent) |
| -{ |
| - this._text = textContent; |
| -} |
| - |
| -ContainsSelector.prototype = { |
| - requiresHiding: true, |
| - |
| - *getSelectors(prefix, subtree, stylesheet) |
| - { |
| - for (let element of this.getElements(prefix, subtree, stylesheet)) |
| - yield [makeSelector(element, ""), subtree]; |
| - }, |
| - |
| - *getElements(prefix, subtree, stylesheet) |
| - { |
| - let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? |
| - prefix + "*" : prefix; |
| - let elements = subtree.querySelectorAll(actualPrefix); |
| - for (let element of elements) |
| - if (element.textContent.includes(this._text)) |
| - yield element; |
| - } |
| -}; |
| - |
| -function PropsSelector(propertyExpression) |
| -{ |
| - let regexpString; |
| - if (propertyExpression.length >= 2 && propertyExpression[0] == "/" && |
| - propertyExpression[propertyExpression.length - 1] == "/") |
| - { |
| - regexpString = propertyExpression.slice(1, -1) |
| - .replace("\\x7B ", "{").replace("\\x7D ", "}"); |
| - } |
| - else |
| - regexpString = filterToRegExp(propertyExpression); |
| - |
| - this._regexp = new RegExp(regexpString, "i"); |
| -} |
| - |
| -PropsSelector.prototype = { |
| - preferHideWithSelector: true, |
| - dependsOnStyles: true, |
| - |
| - *findPropsSelectors(styles, prefix, regexp) |
| - { |
| - for (let style of styles) |
| - if (regexp.test(style.style)) |
| - for (let subSelector of style.subSelectors) |
| - { |
| - let idx = subSelector.lastIndexOf("::"); |
| - if (idx != -1) |
| - subSelector = subSelector.substr(0, idx); |
| - yield prefix + subSelector; |
| - } |
| - }, |
| - |
| - *getSelectors(prefix, subtree, styles) |
| - { |
| - for (let selector of this.findPropsSelectors(styles, prefix, this._regexp)) |
| - yield [selector, subtree]; |
| - } |
| -}; |
| - |
| -function ElemHideEmulation(window, getFiltersFunc, addSelectorsFunc, |
| - hideElemsFunc) |
| -{ |
| - this.window = window; |
| - this.getFiltersFunc = getFiltersFunc; |
| - this.addSelectorsFunc = addSelectorsFunc; |
| - this.hideElemsFunc = hideElemsFunc; |
| -} |
| - |
| -ElemHideEmulation.prototype = { |
| - isSameOrigin(stylesheet) |
| - { |
| - try |
| - { |
| - return new URL(stylesheet.href).origin == this.window.location.origin; |
| - } |
| - catch (e) |
| - { |
| - // Invalid URL, assume that it is first-party. |
| - return true; |
| - } |
| - }, |
| - |
| - /** Parse the selector |
| - * @param {string} selector the selector to parse |
| - * @return {Array} selectors is an array of objects, |
| - * or null in case of errors. |
| - */ |
| - parseSelector(selector) |
| - { |
| - if (selector.length == 0) |
| - return []; |
| - |
| - let match = abpSelectorRegexp.exec(selector); |
| - if (!match) |
| - return [new PlainSelector(selector)]; |
| - |
| - let selectors = []; |
| - if (match.index > 0) |
| - selectors.push(new PlainSelector(selector.substr(0, match.index))); |
| - |
| - let startIndex = match.index + match[0].length; |
| - let content = parseSelectorContent(selector, startIndex); |
| - if (!content) |
| - { |
| - this.window.console.error( |
| - new SyntaxError("Failed to parse Adblock Plus " + |
| - `selector ${selector} ` + |
| - "due to unmatched parentheses.")); |
| - return null; |
| - } |
| - if (match[1] == "properties") |
| - selectors.push(new PropsSelector(content.text)); |
| - else if (match[1] == "has") |
| - { |
| - let hasSelectors = this.parseSelector(content.text); |
| - if (hasSelectors == null) |
| - return null; |
| - selectors.push(new HasSelector(hasSelectors)); |
| - } |
| - else if (match[1] == "contains") |
| - selectors.push(new ContainsSelector(content.text)); |
| - else |
| - { |
| - // this is an error, can't parse selector. |
| - this.window.console.error( |
| - new SyntaxError("Failed to parse Adblock Plus " + |
| - `selector ${selector}, invalid ` + |
| - `pseudo-class :-abp-${match[1]}().`)); |
| - return null; |
| - } |
| - |
| - let suffix = this.parseSelector(selector.substr(content.end + 1)); |
| - if (suffix == null) |
| - return null; |
| - |
| - selectors.push(...suffix); |
| - |
| - if (selectors.length == 1 && selectors[0] instanceof ContainsSelector) |
| - { |
| - this.window.console.error( |
| - new SyntaxError("Failed to parse Adblock Plus " + |
| - `selector ${selector}, can't ` + |
| - "have a lonely :-abp-contains().")); |
| - return null; |
| - } |
| - return selectors; |
| - }, |
| - |
| - _lastInvocation: 0, |
| - |
| - /** |
| - * Processes the current document and applies all rules to it. |
| - * @param {CSSStyleSheet[]} [stylesheets] |
| - * The list of new stylesheets that have been added to the document and |
| - * made reprocessing necessary. This parameter shouldn't be passed in for |
| - * the initial processing, all of document's stylesheets will be considered |
| - * then and all rules, including the ones not dependent on styles. |
| - */ |
| - addSelectors(stylesheets) |
| - { |
| - this._lastInvocation = Date.now(); |
| - |
| - let selectors = []; |
| - let selectorFilters = []; |
| - |
| - let elements = []; |
| - let elementFilters = []; |
| - |
| - let cssStyles = []; |
| - |
| - let stylesheetOnlyChange = !!stylesheets; |
| - if (!stylesheets) |
| - stylesheets = this.window.document.styleSheets; |
| - |
| - // Chrome < 51 doesn't have an iterable StyleSheetList |
| - // https://issues.adblockplus.org/ticket/5381 |
| - for (let i = 0; i < stylesheets.length; i++) |
| - { |
| - let stylesheet = stylesheets[i]; |
| - // Explicitly ignore third-party stylesheets to ensure consistent behavior |
| - // between Firefox and Chrome. |
| - if (!this.isSameOrigin(stylesheet)) |
| - continue; |
| - |
| - let rules = stylesheet.cssRules; |
| - if (!rules) |
| - continue; |
| - |
| - for (let rule of rules) |
| - { |
| - if (rule.type != rule.STYLE_RULE) |
| - continue; |
| - |
| - cssStyles.push(stringifyStyle(rule)); |
| - } |
| - } |
| - |
| - let {document} = this.window; |
| - for (let pattern of this.patterns) |
| - { |
| - if (stylesheetOnlyChange && |
| - !pattern.selectors.some(selector => selector.dependsOnStyles)) |
| - { |
| - continue; |
| - } |
| - |
| - for (let selector of evaluate(pattern.selectors, |
| - 0, "", document, cssStyles)) |
| - { |
| - if (pattern.selectors.some(s => s.preferHideWithSelector) && |
| - !pattern.selectors.some(s => s.requiresHiding)) |
| - { |
| - selectors.push(selector); |
| - selectorFilters.push(pattern.text); |
| - } |
| - else |
| - { |
| - for (let element of document.querySelectorAll(selector)) |
| - { |
| - elements.push(element); |
| - elementFilters.push(pattern.text); |
| - } |
| - } |
| - } |
| - } |
| - |
| - this.addSelectorsFunc(selectors, selectorFilters); |
| - this.hideElemsFunc(elements, elementFilters); |
| - }, |
| - |
| - _stylesheetQueue: null, |
| - |
| - onLoad(event) |
| - { |
| - let stylesheet = event.target.sheet; |
| - if (stylesheet) |
| - { |
| - if (!this._stylesheetQueue && |
| - Date.now() - this._lastInvocation < MIN_INVOCATION_INTERVAL) |
| - { |
| - this._stylesheetQueue = []; |
| - this.window.setTimeout(() => |
| - { |
| - let stylesheets = this._stylesheetQueue; |
| - this._stylesheetQueue = null; |
| - this.addSelectors(stylesheets); |
| - }, MIN_INVOCATION_INTERVAL - (Date.now() - this._lastInvocation)); |
| - } |
| - |
| - if (this._stylesheetQueue) |
| - this._stylesheetQueue.push(stylesheet); |
| - else |
| - this.addSelectors([stylesheet]); |
| - } |
| - }, |
| - |
| - apply() |
| - { |
| - this.getFiltersFunc(patterns => |
| - { |
| - this.patterns = []; |
| - for (let pattern of patterns) |
| - { |
| - let selectors = this.parseSelector(pattern.selector); |
| - if (selectors != null && selectors.length > 0) |
| - this.patterns.push({selectors, text: pattern.text}); |
| - } |
| - |
| - if (this.patterns.length > 0) |
| - { |
| - let {document} = this.window; |
| - this.addSelectors(); |
| - document.addEventListener("load", this.onLoad.bind(this), true); |
| - } |
| - }); |
| - } |
| -}; |