| 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); | 
| -      } | 
| -    }); | 
| -  } | 
| -}; | 
|  |