{"snippets.js": "/*\r\n * This file is part of Adblock Plus <https://adblockplus.org/>,\r\n * Copyright (C) 2006-present eyeo GmbH\r\n *\r\n * Adblock Plus is free software: you can redistribute it and/or modify\r\n * it under the terms of the GNU General Public License version 3 as\r\n * published by the Free Software Foundation.\r\n *\r\n * Adblock Plus is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>.\r\n */\r\n    \"use strict\";\r\n    {\r\n      let libraries = [\"\\n\\n/** @module */\\n\\n/* global environment */\\n/* eslint-env webextensions */\\n/* eslint no-console: \\\"off\\\" */\\n\\n\\\"use strict\\\";\\n\\n// secured env and secured global variables\\nlet ABP = getABPNamespace();\\nlet {Object, utils, DOM} = ABP;\\nlet {getComputedStyle, setInterval, setTimeout, performance} = ABP.window;\\n\\n/**\\n * @typedef {object} FetchContentInfo\\n * @property {function} remove\\n * @property {Promise} result\\n * @property {number} timer\\n * @private\\n */\\n\\n/**\\n * @type {Map.<string, FetchContentInfo>}\\n * @private\\n */\\nlet fetchContentMap = new Map();\\n\\n/**\\n * @type {Map.<function, Array.<function>>}\\n * @private\\n */\\nlet dependencyGraph = new Map();\\n\\n/**\\n * @type {Array.<string>}\\n * @private\\n */\\nlet injectionCode = [];\\n\\n/**\\n * @type {Set.<function>}\\n * @private\\n */\\nlet injectionDependencies = new Set();\\n\\n/**\\n * Prepare injection transaction by resetting code and dependencies.\\n *\\n * @private\\n */\\nexports.prepareInjection = () =>\\n{\\n  injectionCode.splice(0);\\n  injectionDependencies.clear();\\n};\\n\\n/**\\n * Commit JavaScript code injection into the document using a temporary\\n * `script` element.\\n *\\n * @private\\n */\\nexports.commitInjection = () =>\\n{\\n  if (injectionCode.length < 1)\\n    return;\\n\\n  // retrieve code and dependencies while cleaning up all related data\\n  let code = injectionCode.splice(0);\\n  let dependencies = [...injectionDependencies];\\n  injectionDependencies.clear();\\n\\n  let executable = `\\n    (function()\\n    {\\n      \\\"use strict\\\";\\n      let secured = (Class) =>\\n      {\\n        class Secured extends Class {};\\n        let a = Class.prototype;\\n        let b = Secured.prototype;\\n        for (let key of Reflect.ownKeys(a))\\n        {\\n          let desc = window.Object.getOwnPropertyDescriptor(a, key);\\n          desc.configurable = false;\\n          if (desc.writable)\\n            desc.writable = false;\\n          window.Object.defineProperty(b, key, desc);\\n        }\\n        return Secured;\\n      };\\n      let MutationObserver = secured(window.MutationObserver);\\n      let DOMParser = secured(window.DOMParser);\\n      let Set = secured(window.Set);\\n      let Array = secured(window.Array);\\n      let environment = ${JSON.stringify(environment)};\\n      let ABP = (${getABPNamespace})();\\n      let {Object, utils, DOM} = ABP;\\n      let {getComputedStyle, setInterval, setTimeout,\\n           performance} = ABP.window;\\n      let debug = ${debug};\\n      let inactiveProfile = ${inactiveProfile};\\n      let noopProfile = ${noopProfile};\\n      ${dependencies.join(\\\"\\\\n\\\")}\\n      ${code.join(\\\"\\\\n\\\")}\\n    })();\\n  `;\\n\\n  let script = document.createElement(\\\"script\\\");\\n\\n  script.type = \\\"application/javascript\\\";\\n  script.async = false;\\n\\n  // Firefox 58 only bypasses site CSPs when assigning to 'src',\\n  // while Chrome 67 and Microsoft Edge (tested on 44.17763.1.0)\\n  // only bypass site CSPs when using 'textContent'.\\n  if (\\\"\\\".startsWith(\\\"moz-extension://\\\"))\\n  {\\n    let url = URL.createObjectURL(new Blob([executable]));\\n    script.src = url;\\n    document.documentElement.appendChild(script);\\n    URL.revokeObjectURL(url);\\n  }\\n  else\\n  {\\n    script.textContent = executable;\\n    document.documentElement.appendChild(script);\\n  }\\n\\n  document.documentElement.removeChild(script);\\n};\\n\\n/**\\n * Extract utilities from globals and return a deep-frozen object with those.\\n * @return {object} An object namespace with all the global utilities used by\\n * our snippets.\\n * @private\\n */\\nfunction getABPNamespace()\\n{\\n  /* eslint-disable no-shadow */\\n  let {Object} = window;\\n  let {assign, defineProperty, freeze, getOwnPropertyDescriptor,\\n       values} = window.Object;\\n  let getParentNode = getOwnPropertyDescriptor(window.Node.prototype,\\n                                               \\\"parentNode\\\").get;\\n  let {getComputedStyle, setInterval, setTimeout, performance} = window;\\n  let create = (Class, args = []) =>\\n  {\\n    let out = new Class(...args);\\n    for (let key of Reflect.ownKeys(Class.prototype))\\n      defineProperty(out, key, getOwnPropertyDescriptor(Class.prototype, key));\\n    return freeze(out);\\n  };\\n  /* eslint-enable no-shadow */\\n  // the bind is needed in Firefox or it breaks\\n  return freeze({\\n    Object: freeze({\\n      assign: assign.bind(Object),\\n      defineProperty: defineProperty.bind(Object),\\n      getOwnPropertyDescriptor: getOwnPropertyDescriptor.bind(Object),\\n      values: (values || function(object)\\n      {\\n        return this.keys(object).map(key => object[key]);\\n      }).bind(Object)\\n    }),\\n    utils: freeze({\\n      isOwnProperty: Function.call.bind(Object.prototype.hasOwnProperty)\\n    }),\\n    window: freeze({\\n      getComputedStyle: getComputedStyle.bind(window),\\n      setInterval: setInterval.bind(window),\\n      setTimeout: setTimeout.bind(window),\\n      performance\\n    }),\\n    DOM: freeze({\\n      getParentNode,\\n      document: freeze({\\n        $$: document.querySelectorAll.bind(document)\\n      }),\\n      matches: Function.call.bind(Element.prototype.matches),\\n      frozen: create(WeakMap),\\n      hidden: create(WeakSet),\\n      iframePropertiesToAbort: {\\n        read: create(Set),\\n        write: create(Set)\\n      },\\n      abortedIframes: create(WeakMap)\\n    })\\n  });\\n}\\n\\n/**\\n * Register one or more dependencies for a specific function.\\n * @param {function} func The function that requires dependencies.\\n * @param {...function} dependencies The function function dependencies.\\n * @private\\n */\\nfunction registerDependencies(func, ...dependencies)\\n{\\n  if (dependencyGraph.has(func))\\n    throw new Error(`duplicated ${func.name} dependencies`);\\n\\n  dependencyGraph.set(func, dependencies);\\n}\\n\\n/**\\n * Returns a list of requirements for a function being injected as a script.\\n * @param {function} func A function with dependencies.\\n * @param {Set} [dependencies] An object that collects the unique set of\\n * dependencies.\\n * @private\\n */\\nfunction resolveDependencies(func)\\n{\\n  injectionDependencies.add(func);\\n\\n  for (let dependency of dependencyGraph.get(func) || [])\\n  {\\n    if (!injectionDependencies.has(dependency))\\n    {\\n      injectionDependencies.add(dependency);\\n      resolveDependencies(dependency);\\n    }\\n  }\\n}\\n\\n/**\\n * Returns a potentially already resolved fetch auto cleaning, if not requested\\n * again, after a certain amount of milliseconds.\\n *\\n * The resolved fetch is by default `arrayBuffer` but it can be any other kind\\n * through the configuration object.\\n *\\n * @param {string} url The url to fetch\\n * @param {object} [options] Optional configuration options.\\n *                            By default is {as: \\\"arrayBuffer\\\", cleanup: 60000}\\n * @param {string} [options.as] The fetch type: \\\"arrayBuffer\\\", \\\"json\\\", \\\"text\\\"..\\n * @param {number} [options.cleanup] The cache auto-cleanup delay in ms: 60000\\n *\\n * @returns {Promise} The fetched result as Uint8Array|string.\\n *\\n * @example\\n * fetchContent('https://any.url.com').then(arrayBuffer => { ... })\\n * @example\\n * fetchContent('https://a.com', {as: 'json'}).then(json => { ... })\\n * @example\\n * fetchContent('https://a.com', {as: 'text'}).then(text => { ... })\\n * @private\\n */\\nfunction fetchContent(url, {as = \\\"arrayBuffer\\\", cleanup = 60000} = {})\\n{\\n  // make sure the fetch type is unique as the url fetching text or arrayBuffer\\n  // will fetch same url twice but it will resolve it as expected instead of\\n  // keeping the fetch potentially hanging forever.\\n  let uid = as + \\\":\\\" + url;\\n  let details = fetchContentMap.get(uid) || {\\n    remove: () => fetchContentMap.delete(uid),\\n    result: null,\\n    timer: 0\\n  };\\n  clearTimeout(details.timer);\\n  details.timer = setTimeout(details.remove, cleanup);\\n  if (!details.result)\\n  {\\n    details.result = fetch(url).then(response => response[as]());\\n    details.result.catch(details.remove);\\n    fetchContentMap.set(uid, details);\\n  }\\n  return details.result;\\n}\\n\\n/**\\n * Escapes regular expression special characters in a string.\\n *\\n * The returned string may be passed to the `RegExp` constructor to match the\\n * original string.\\n *\\n * @param {string} string The string in which to escape special characters.\\n *\\n * @returns {string} A new string with the special characters escaped.\\n * @private\\n */\\nfunction regexEscape(string)\\n{\\n  return string.replace(/[-/\\\\\\\\^$*+?.()|[\\\\]{}]/g, \\\"\\\\\\\\$&\\\");\\n}\\n\\n/**\\n * Converts a given pattern to a regular expression.\\n *\\n * @param {string} pattern The pattern to convert. If the pattern begins and\\n *   ends with a slash (`/`), the text in between is treated as a regular\\n *   expression; otherwise the pattern is treated as raw text.\\n *\\n * @returns {RegExp} A `RegExp` object based on the given pattern.\\n * @private\\n */\\nfunction toRegExp(pattern)\\n{\\n  if (pattern.length >= 2 && pattern[0] == \\\"/\\\" &&\\n      pattern[pattern.length - 1] == \\\"/\\\")\\n    return new RegExp(pattern.substring(1, pattern.length - 1));\\n\\n  return new RegExp(regexEscape(pattern));\\n}\\n\\nregisterDependencies(toRegExp, regexEscape);\\n\\n/**\\n * Converts a number to its hexadecimal representation.\\n *\\n * @param {number} number The number to convert.\\n * @param {number} [length] The <em>minimum</em> length of the hexadecimal\\n *   representation. For example, given the number `1024` and the length `8`,\\n *   the function returns the value `\\\"00000400\\\"`.\\n *\\n * @returns {string} The hexadecimal representation of the given number.\\n * @private\\n */\\nfunction toHex(number, length = 2)\\n{\\n  let hex = number.toString(16);\\n\\n  if (hex.length < length)\\n    hex = \\\"0\\\".repeat(length - hex.length) + hex;\\n\\n  return hex;\\n}\\n\\n/**\\n * Converts a `Uint8Array` object into its hexadecimal representation.\\n *\\n * @param {Uint8Array} uint8Array The `Uint8Array` object to convert.\\n *\\n * @returns {string} The hexadecimal representation of the given `Uint8Array`\\n *   object.\\n * @private\\n */\\nfunction uint8ArrayToHex(uint8Array)\\n{\\n  return uint8Array.reduce((hex, byte) => hex + toHex(byte), \\\"\\\");\\n}\\n\\n/**\\n * Returns the value of the `cssText` property of the object returned by\\n * `getComputedStyle` for the given element.\\n *\\n * If the value of the `cssText` property is blank, this function computes the\\n * value out of the properties available in the object.\\n *\\n * @param {Element} element The element for which to get the computed CSS text.\\n *\\n * @returns {string} The computed CSS text.\\n * @private\\n */\\nfunction getComputedCSSText(element)\\n{\\n  let style = getComputedStyle(element);\\n  let {cssText} = style;\\n\\n  if (cssText)\\n    return cssText;\\n\\n  for (let property of style)\\n    cssText += `${property}: ${style[property]}; `;\\n\\n  return cssText.trim();\\n}\\n\\n/**\\n * Converts a function and an optional list of arguments into a string of code\\n * containing a function call.\\n *\\n * The function is converted to its string representation using the\\n * `Function.prototype.toString` method. Each argument is stringified using\\n * `JSON.stringify`.\\n *\\n * @param {function} func The function to convert.\\n * @param {...*} [params] The arguments to convert.\\n *\\n * @returns {string} The generated code containing the function call.\\n * @private\\n */\\nfunction stringifyFunctionCall(func, ...params)\\n{\\n  // Call JSON.stringify on the arguments to avoid any arbitrary code\\n  // execution.\\n  return `${func.name}(${params.map(JSON.stringify).join(\\\",\\\")});`;\\n}\\n\\n/**\\n * Wraps a function and its dependencies into an injector.\\n *\\n * The injector, when called with zero or more arguments, generates code that\\n * calls the function, with the given arguments, if any, and injects the code,\\n * along with any dependencies, into the document using a temporary `script`\\n * element.\\n *\\n * @param {function} injectable The function to wrap into an injector.\\n * @param {...(function|string)} [dependencies] Any dependencies of the\\n *   function. A dependency may be either a function or a string containing\\n *   some executable code.\\n *\\n * @returns {function} The generated injector.\\n * @private\\n */\\nfunction makeInjector(injectable)\\n{\\n  return (...args) =>\\n  {\\n    resolveDependencies(injectable);\\n    injectionCode.push(stringifyFunctionCall(injectable, ...args));\\n  };\\n}\\n\\n/**\\n * Hides an HTML element by setting its `style` attribute to\\n * `display: none !important`.\\n *\\n * @param {HTMLElement} element The HTML element to hide.\\n * @private\\n */\\nfunction hideElement(element)\\n{\\n  if (ABP.DOM.hidden.has(element))\\n    return;\\n\\n  ABP.DOM.hidden.add(element);\\n\\n  let {style} = element;\\n  let properties = [];\\n\\n  for (let [key, value] of environment.debugCSSProperties ||\\n                           [[\\\"display\\\", \\\"none\\\"]])\\n  {\\n    style.setProperty(key, value, \\\"important\\\");\\n    properties.push([key, style.getPropertyValue(key)]);\\n  }\\n\\n  // Listen for changes to the style property and if our values are unset\\n  // then reset them.\\n  new MutationObserver(() =>\\n  {\\n    for (let [key, value] of properties)\\n    {\\n      if (style.getPropertyValue(key) != value ||\\n          style.getPropertyPriority(key) != \\\"important\\\")\\n        style.setProperty(key, value, \\\"important\\\");\\n    }\\n  }).observe(element, {attributes: true,\\n                       attributeFilter: [\\\"style\\\"]});\\n}\\n\\n/**\\n * Logs its arguments to the console.\\n *\\n * This may be used for testing and debugging.\\n *\\n * @alias module:content/snippets.log\\n *\\n * @param {...*} [args] The arguments to log.\\n *\\n * @since Adblock Plus 3.3\\n */\\nfunction log(...args)\\n{\\n  let {mark, end} = profile(\\\"log\\\");\\n\\n  if (debug)\\n    args.unshift(\\\"%c DEBUG\\\", \\\"font-weight: bold\\\");\\n\\n  mark();\\n  console.log(...args);\\n  end();\\n}\\n\\nregisterDependencies(log, profile);\\n\\nexports.log = log;\\n\\n/**\\n * Similar to `log`, but does the logging in the context of the document rather\\n * than the content script.\\n *\\n * This may be used for testing and debugging, especially to verify that the\\n * injection of snippets into the document is working without any errors.\\n *\\n * @param {...*} [args] The arguments to log.\\n *\\n * @since Adblock Plus 3.3\\n */\\nfunction trace(...args)\\n{\\n  // We could simply use console.log here, but the goal is to demonstrate the\\n  // usage of snippet dependencies.\\n  log(...args);\\n}\\n\\nregisterDependencies(trace, log);\\n\\nexports.trace = makeInjector(trace);\\n\\n/**\\n * This is an implementation of the `uabinject-defuser` technique used by\\n * [uBlock Origin](https://github.com/uBlockOrigin/uAssets/blob/c091f861b63cd2254b8e9e4628f6bdcd89d43caa/filters/resources.txt#L640).\\n * @alias module:content/snippets.uabinject-defuser\\n *\\n * @since Adblock Plus 3.3\\n */\\nfunction uabinjectDefuser()\\n{\\n  window.trckd = true;\\n  window.uabpdl = true;\\n  window.uabInject = true;\\n  window.uabDetect = true;\\n}\\n\\nexports[\\\"uabinject-defuser\\\"] = makeInjector(uabinjectDefuser);\\n\\n/**\\n * Hides any HTML element or one of its ancestors matching a CSS selector if\\n * the text content of the element's shadow contains a given string.\\n * @alias module:content/snippets.hide-if-shadow-contains\\n *\\n * @param {string} search The string to look for in every HTML element's\\n *   shadow. If the string begins and ends with a slash (`/`), the text in\\n *   between is treated as a regular expression.\\n * @param {string} selector The CSS selector that an HTML element must match\\n *   for it to be hidden.\\n *\\n * @since Adblock Plus 3.3\\n */\\nfunction hideIfShadowContains(search, selector = \\\"*\\\")\\n{\\n  let originalAttachShadow = Element.prototype.attachShadow;\\n\\n  // If there's no Element.attachShadow API present then we don't care, it must\\n  // be Firefox or an older version of Chrome.\\n  if (!originalAttachShadow)\\n    return;\\n\\n  let re = toRegExp(search);\\n\\n  // Mutation observers mapped to their corresponding shadow roots and their\\n  // hosts.\\n  let shadows = new WeakMap();\\n\\n  function observeShadow(mutations, observer)\\n  {\\n    let {host, root} = shadows.get(observer) || {};\\n\\n    // Since it's a weak map, it's possible that either the element or its\\n    // shadow has been removed.\\n    if (!host || !root)\\n      return;\\n\\n    // If the shadow contains the given text, check if the host or one of its\\n    // ancestors matches the selector; if a matching element is found, hide\\n    // it.\\n    if (re.test(root.textContent))\\n    {\\n      let closest = host.closest(selector);\\n      if (closest)\\n        hideElement(closest);\\n    }\\n  }\\n\\n  Object.defineProperty(Element.prototype, \\\"attachShadow\\\", {\\n    value(...args)\\n    {\\n      // Create the shadow root first. It doesn't matter if it's a closed\\n      // shadow root, we keep the reference in a weak map.\\n      let root = originalAttachShadow.apply(this, args);\\n\\n      // Listen for relevant DOM mutations in the shadow.\\n      let observer = new MutationObserver(observeShadow);\\n      observer.observe(root, {\\n        childList: true,\\n        characterData: true,\\n        subtree: true\\n      });\\n\\n      // Keep references to the shadow root and its host in a weak map. If\\n      // either the shadow is detached or the host itself is removed from the\\n      // DOM, the mutation observer too will be freed eventually and the entry\\n      // will be removed.\\n      shadows.set(observer, {host: this, root});\\n\\n      return root;\\n    }\\n  });\\n}\\n\\nregisterDependencies(hideIfShadowContains,\\n                     toRegExp,\\n                     hideElement);\\n\\nexports[\\\"hide-if-shadow-contains\\\"] = makeInjector(hideIfShadowContains);\\n\\n/**\\n * Hides any HTML element or one of its ancestors matching a CSS selector if\\n * it matches the provided condition.\\n *\\n * @param {function} match The function that provides the matching condition.\\n * @param {string} selector The CSS selector that an HTML element must match\\n *   for it to be hidden.\\n * @param {?string} [searchSelector] The CSS selector that an HTML element\\n *   containing the given string must match. Defaults to the value of the\\n *   `selector` argument.\\n * @private\\n */\\nfunction hideIfMatches(match, selector, searchSelector)\\n{\\n  if (searchSelector == null)\\n    searchSelector = selector;\\n\\n  let callback = () =>\\n  {\\n    for (let element of document.querySelectorAll(searchSelector))\\n    {\\n      let closest = element.closest(selector);\\n      if (closest && match(element, closest))\\n        hideElement(closest);\\n    }\\n  };\\n  new MutationObserver(callback)\\n    .observe(document, {childList: true, characterData: true, subtree: true});\\n  callback();\\n}\\n\\n/**\\n * Hides any HTML element or one of its ancestors matching a CSS selector if\\n * the text content of the element contains a given string.\\n * @alias module:content/snippets.hide-if-contains\\n *\\n * @param {string} search The string to look for in HTML elements. If the\\n *   string begins and ends with a slash (`/`), the text in between is treated\\n *   as a regular expression.\\n * @param {string} selector The CSS selector that an HTML element must match\\n *   for it to be hidden.\\n * @param {?string} [searchSelector] The CSS selector that an HTML element\\n *   containing the given string must match. Defaults to the value of the\\n *   `selector` argument.\\n *\\n * @since Adblock Plus 3.3\\n */\\nfunction hideIfContains(search, selector = \\\"*\\\", searchSelector = null)\\n{\\n  let re = toRegExp(search);\\n\\n  hideIfMatches(element => re.test(element.textContent),\\n                selector, searchSelector);\\n}\\n\\nexports[\\\"hide-if-contains\\\"] = hideIfContains;\\n\\n/**\\n * Check if an element is visible\\n *\\n * @param {Element} element The element to check visibility of.\\n * @param {CSSStyleDeclaration} style The computed style of element.\\n * @param {?Element} closest The closest parent to reach.\\n * @return {bool} Whether the element is visible.\\n * @private\\n */\\nfunction isVisible(element, style, closest)\\n{\\n  if (style.getPropertyValue(\\\"display\\\") == \\\"none\\\")\\n    return false;\\n\\n  let visibility = style.getPropertyValue(\\\"visibility\\\");\\n  if (visibility == \\\"hidden\\\" || visibility == \\\"collapse\\\")\\n    return false;\\n\\n  if (!closest || element == closest)\\n    return true;\\n\\n  let parent = element.parentElement;\\n  if (!parent)\\n    return true;\\n\\n  return isVisible(parent, getComputedStyle(parent), closest);\\n}\\n\\n/**\\n * Hides any HTML element matching a CSS selector if the visible text content\\n * of the element contains a given string.\\n * @alias module:content/snippets.hide-if-contains-visible-text\\n *\\n * @param {string} search The string to match to the visible text. Is considered\\n *   visible text that isn't hidden by CSS properties or other means.\\n *   If the string begins and ends with a slash (`/`), the text in between is\\n *   treated as a regular expression.\\n * @param {string} selector The CSS selector that an HTML element must match\\n *   for it to be hidden.\\n * @param {?string} [searchSelector] The CSS selector that an HTML element\\n *   containing the given string must match. Defaults to the value of the\\n *   `selector` argument.\\n *\\n * @since Adblock Plus 3.6\\n */\\nfunction hideIfContainsVisibleText(search, selector, searchSelector = null)\\n{\\n  /**\\n   * Determines if the text inside the element is visible.\\n   *\\n   * @param {Element} element The element we are checking.\\n   * @param {?CSSStyleDeclaration} style The computed style of element. If\\n   *   falsey it will be queried.\\n   * @returns {bool} Whether the text is visible.\\n   * @private\\n   */\\n  function isTextVisible(element, style)\\n  {\\n    if (!style)\\n      style = getComputedStyle(element);\\n\\n    if (style.getPropertyValue(\\\"opacity\\\") == \\\"0\\\")\\n      return false;\\n    if (style.getPropertyValue(\\\"font-size\\\") == \\\"0px\\\")\\n      return false;\\n\\n    let color = style.getPropertyValue(\\\"color\\\");\\n    // if color is transparent...\\n    if (color == \\\"rgba(0, 0, 0, 0)\\\")\\n      return false;\\n    if (style.getPropertyValue(\\\"background-color\\\") == color)\\n      return false;\\n\\n    return true;\\n  }\\n\\n  /**\\n   * Check if a pseudo element has visible text via `content`.\\n   *\\n   * @param {Element} element The element to check visibility of.\\n   * @param {string} pseudo The `::before` or `::after` pseudo selector.\\n   * @return {string} The pseudo content or an empty string.\\n   * @private\\n   */\\n  function getPseudoContent(element, pseudo)\\n  {\\n    let style = getComputedStyle(element, pseudo);\\n    if (!isVisible(element, style) || !isTextVisible(element, style))\\n      return \\\"\\\";\\n\\n    let {content} = style;\\n    if (content && content !== \\\"none\\\")\\n    {\\n      let strings = [];\\n\\n      // remove all strings, in quotes, including escaping chars, putting\\n      // instead `\\\\x01${string-index}` in place, which is not valid CSS,\\n      // so that it's safe to parse it back at the end of the operation.\\n      content = content.trim().replace(\\n        /([\\\"'])(?:(?=(\\\\\\\\?))\\\\2.)*?\\\\1/g,\\n        value => `\\\\x01${strings.push(value.slice(1, -1)) - 1}`\\n      );\\n\\n      // replace attr(...) with the attribute value or an empty string,\\n      // ignoring units and fallback values, as these do not work, or have,\\n      // any meaning in the CSS `content` property value.\\n      content = content.replace(\\n        /\\\\s*attr\\\\(\\\\s*([^\\\\s,)]+)[^)]*?\\\\)\\\\s*/g,\\n        (_, name) => element.getAttribute(name) || \\\"\\\"\\n      );\\n\\n      // replace back all `\\\\x01${string-index}` values with their corresponding\\n      // strings, so that the outcome is a real, cleaned up, `content` value.\\n      return content.replace(/\\\\x01(\\\\d+)/g, (_, index) => strings[index]);\\n    }\\n    return \\\"\\\";\\n  }\\n\\n  /**\\n   * Returns the visible text content from an element and its descendants.\\n   *\\n   * @param {Element} element The element whose visible text we want.\\n   * @param {Element} closest The closest parent to reach while checking\\n   *   for visibility.\\n   * @param {?CSSStyleDeclaration} style The computed style of element. If\\n   *   falsey it will be queried.\\n   * @returns {string} The text that is visible.\\n   * @private\\n   */\\n  function getVisibleContent(element, closest, style)\\n  {\\n    let checkClosest = !style;\\n    if (checkClosest)\\n      style = getComputedStyle(element);\\n\\n    if (!isVisible(element, style, checkClosest && closest))\\n      return \\\"\\\";\\n\\n    let text = getPseudoContent(element, \\\":before\\\");\\n    for (let node of element.childNodes)\\n    {\\n      switch (node.nodeType)\\n      {\\n        case Node.ELEMENT_NODE:\\n          text += getVisibleContent(node,\\n                                    element,\\n                                    getComputedStyle(node));\\n          break;\\n        case Node.TEXT_NODE:\\n          if (isTextVisible(element, style))\\n            text += node.nodeValue;\\n          break;\\n      }\\n    }\\n    return text + getPseudoContent(element, \\\":after\\\");\\n  }\\n\\n  let re = toRegExp(search);\\n  let seen = new WeakSet();\\n\\n  hideIfMatches(\\n    (element, closest) =>\\n    {\\n      if (seen.has(element))\\n        return false;\\n\\n      seen.add(element);\\n      let text = getVisibleContent(element, closest);\\n      let result = re.test(text);\\n      if (debug && text.length)\\n        log(result, re, text);\\n      return result;\\n    },\\n    selector,\\n    searchSelector\\n  );\\n}\\n\\nexports[\\\"hide-if-contains-visible-text\\\"] = hideIfContainsVisibleText;\\n\\n/**\\n * Hides any HTML element or one of its ancestors matching a CSS selector if\\n * the text content of the element contains a given string and, optionally, if\\n * the element's computed style contains a given string.\\n * @alias module:content/snippets.hide-if-contains-and-matches-style\\n *\\n * @param {string} search The string to look for in HTML elements. If the\\n *   string begins and ends with a slash (`/`), the text in between is treated\\n *   as a regular expression.\\n * @param {string} selector The CSS selector that an HTML element must match\\n *   for it to be hidden.\\n * @param {string?} [searchSelector] The CSS selector that an HTML element\\n *   containing the given string must match. Defaults to the value of the\\n *   `selector` argument.\\n * @param {string?} [style] The string that the computed style of an HTML\\n *   element matching `selector` must contain. If the string begins and ends\\n *   with a slash (`/`), the text in between is treated as a regular\\n *   expression.\\n * @param {string?} [searchStyle] The string that the computed style of an HTML\\n *   element matching `searchSelector` must contain. If the string begins and\\n *   ends with a slash (`/`), the text in between is treated as a regular\\n *   expression.\\n *\\n * @since Adblock Plus 3.3.2\\n */\\nfunction hideIfContainsAndMatchesStyle(search, selector = \\\"*\\\",\\n                                       searchSelector = null, style = null,\\n                                       searchStyle = null)\\n{\\n  if (searchSelector == null)\\n    searchSelector = selector;\\n\\n  let searchRegExp = toRegExp(search);\\n\\n  let styleRegExp = style ? toRegExp(style) : null;\\n  let searchStyleRegExp = searchStyle ? toRegExp(searchStyle) : null;\\n\\n  new MutationObserver(() =>\\n  {\\n    for (let element of document.querySelectorAll(searchSelector))\\n    {\\n      if (searchRegExp.test(element.textContent) &&\\n          (!searchStyleRegExp ||\\n           searchStyleRegExp.test(getComputedCSSText(element))))\\n      {\\n        let closest = element.closest(selector);\\n        if (closest && (!styleRegExp ||\\n                        styleRegExp.test(getComputedCSSText(closest))))\\n          hideElement(closest);\\n      }\\n    }\\n  })\\n  .observe(document, {childList: true, characterData: true, subtree: true});\\n}\\n\\nexports[\\\"hide-if-contains-and-matches-style\\\"] = hideIfContainsAndMatchesStyle;\\n\\n/**\\n * Hides any HTML element or one of its ancestors matching a CSS selector if a\\n * descendant of the element matches a given CSS selector and, optionally, if\\n * the element's computed style contains a given string.\\n * @alias module:content/snippets.hide-if-has-and-matches-style\\n *\\n * @param {string} search The CSS selector against which to match the\\n *   descendants of HTML elements.\\n * @param {string} selector The CSS selector that an HTML element must match\\n *   for it to be hidden.\\n * @param {?string} [searchSelector] The CSS selector that an HTML element\\n *   containing the specified descendants must match. Defaults to the value of\\n *   the `selector` argument.\\n * @param {?string} [style] The string that the computed style of an HTML\\n *   element matching `selector` must contain. If the string begins and ends\\n *   with a slash (`/`), the text in between is treated as a regular\\n *   expression.\\n * @param {?string} [searchStyle] The string that the computed style of an HTML\\n *   element matching `searchSelector` must contain. If the string begins and\\n *   ends with a slash (`/`), the text in between is treated as a regular\\n *   expression.\\n *\\n * @since Adblock Plus 3.4.2\\n */\\nfunction hideIfHasAndMatchesStyle(search, selector = \\\"*\\\",\\n                                  searchSelector = null, style = null,\\n                                  searchStyle = null)\\n{\\n  if (searchSelector == null)\\n    searchSelector = selector;\\n\\n  let styleRegExp = style ? toRegExp(style) : null;\\n  let searchStyleRegExp = searchStyle ? toRegExp(searchStyle) : null;\\n\\n  new MutationObserver(() =>\\n  {\\n    for (let element of document.querySelectorAll(searchSelector))\\n    {\\n      if (element.querySelector(search) &&\\n          (!searchStyleRegExp ||\\n           searchStyleRegExp.test(getComputedCSSText(element))))\\n      {\\n        let closest = element.closest(selector);\\n        if (closest && (!styleRegExp ||\\n                        styleRegExp.test(getComputedCSSText(closest))))\\n          hideElement(closest);\\n      }\\n    }\\n  })\\n  .observe(document, {childList: true, subtree: true});\\n}\\n\\nexports[\\\"hide-if-has-and-matches-style\\\"] = hideIfHasAndMatchesStyle;\\n\\n/**\\n * Hides any HTML element or one of its ancestors matching a CSS selector if\\n * the background image of the element matches a given pattern.\\n * @alias module:content/snippets.hide-if-contains-image\\n *\\n * @param {string} search The pattern to look for in the background images of\\n *   HTML elements. This must be the hexadecimal representation of the image\\n *   data for which to look. If the string begins and ends with a slash (`/`),\\n *   the text in between is treated as a regular expression.\\n * @param {string} selector The CSS selector that an HTML element must match\\n *   for it to be hidden.\\n * @param {?string} [searchSelector] The CSS selector that an HTML element\\n *   containing the given pattern must match. Defaults to the value of the\\n *   `selector` argument.\\n *\\n * @since Adblock Plus 3.4.2\\n */\\nfunction hideIfContainsImage(search, selector, searchSelector)\\n{\\n  if (searchSelector == null)\\n    searchSelector = selector;\\n\\n  let searchRegExp = toRegExp(search);\\n\\n  new MutationObserver(() =>\\n  {\\n    for (let element of document.querySelectorAll(searchSelector))\\n    {\\n      let style = getComputedStyle(element);\\n      let match = style[\\\"background-image\\\"].match(/^url\\\\(\\\"(.*)\\\"\\\\)$/);\\n      if (match)\\n      {\\n        fetchContent(match[1]).then(content =>\\n        {\\n          if (searchRegExp.test(uint8ArrayToHex(new Uint8Array(content))))\\n          {\\n            let closest = element.closest(selector);\\n            if (closest)\\n              hideElement(closest);\\n          }\\n        });\\n      }\\n    }\\n  })\\n  .observe(document, {childList: true, subtree: true});\\n}\\n\\nexports[\\\"hide-if-contains-image\\\"] = hideIfContainsImage;\\n\\n/**\\n * Wraps the `console.dir` API to call the `toString` method of the argument.\\n * @alias module:content/snippets.dir-string\\n *\\n * @param {string} [times=1] The number of times to call the `toString` method\\n *   of the argument to `console.dir`.\\n *\\n * @since Adblock Plus 3.4\\n */\\nfunction dirString(times = \\\"1\\\")\\n{\\n  let {dir} = console;\\n\\n  console.dir = function(object)\\n  {\\n    for (let i = 0; i < times; i++)\\n      object + \\\"\\\";\\n\\n    if (typeof dir == \\\"function\\\")\\n      dir.call(this, object);\\n  };\\n}\\n\\nexports[\\\"dir-string\\\"] = makeInjector(dirString);\\n\\n/**\\n * Generates a random alphanumeric ID consisting of 6 base-36 digits\\n * from the range 100000..zzzzzz (both inclusive).\\n *\\n * @returns {string} The random ID.\\n * @private\\n */\\nfunction randomId()\\n{\\n  // 2176782336 is 36^6 which mean 6 chars [a-z0-9]\\n  // 60466176 is 36^5\\n  // 2176782336 - 60466176 = 2116316160. This ensure to always have 6\\n  // chars even if Math.random() returns its minimum value 0.0\\n  //\\n  return Math.floor(Math.random() * 2116316160 + 60466176).toString(36);\\n}\\n\\nfunction wrapPropertyAccess(object, property, descriptor)\\n{\\n  let dotIndex = property.indexOf(\\\".\\\");\\n  if (dotIndex == -1)\\n  {\\n    // simple property case.\\n    let currentDescriptor = Object.getOwnPropertyDescriptor(object, property);\\n    if (currentDescriptor && !currentDescriptor.configurable)\\n      return;\\n\\n    // Keep it configurable because the same property can be wrapped via\\n    // multiple snippet filters (#7373).\\n    let newDescriptor = Object.assign({}, descriptor, {configurable: true});\\n\\n    if (!currentDescriptor && !newDescriptor.get && newDescriptor.set)\\n    {\\n      let propertyValue = object[property];\\n      newDescriptor.get = () => propertyValue;\\n    }\\n\\n    Object.defineProperty(object, property, newDescriptor);\\n    return;\\n  }\\n\\n  let name = property.slice(0, dotIndex);\\n  property = property.slice(dotIndex + 1);\\n  let value = object[name];\\n  if (value && (typeof value == \\\"object\\\" || typeof value == \\\"function\\\"))\\n    wrapPropertyAccess(value, property, descriptor);\\n\\n  let currentDescriptor = Object.getOwnPropertyDescriptor(object, name);\\n  if (currentDescriptor && !currentDescriptor.configurable)\\n    return;\\n\\n  let setter = newValue =>\\n  {\\n    value = newValue;\\n    if (newValue && (typeof newValue == \\\"object\\\" || typeof value == \\\"function\\\"))\\n      wrapPropertyAccess(newValue, property, descriptor);\\n  };\\n\\n  Object.defineProperty(object, name, {\\n    get: () => value,\\n    set: setter,\\n    configurable: true\\n  });\\n}\\n\\n/**\\n * Overrides the `onerror` handler to discard tagged error messages from our\\n * property wrapping.\\n *\\n * @param {string} magic The magic string that tags the error message.\\n * @private\\n */\\nfunction overrideOnError(magic)\\n{\\n  let {onerror} = window;\\n  window.onerror = (message, ...rest) =>\\n  {\\n    if (typeof message == \\\"string\\\" && message.includes(magic))\\n      return true;\\n    if (typeof onerror == \\\"function\\\")\\n      return (() => {}).call.call(onerror, this, message, ...rest);\\n  };\\n}\\n\\n/**\\n * Patches a property on the window object to abort execution when the\\n * property is read.\\n *\\n * No error is printed to the console.\\n *\\n * The idea originates from\\n * [uBlock Origin](https://github.com/uBlockOrigin/uAssets/blob/80b195436f8f8d78ba713237bfc268ecfc9d9d2b/filters/resources.txt#L1703).\\n * @alias module:content/snippets.abort-on-property-read\\n *\\n * @param {string} property The name of the property.\\n *\\n * @since Adblock Plus 3.4.1\\n */\\nfunction abortOnPropertyRead(property)\\n{\\n  abortOnRead(\\\"abort-on-property-read\\\", window, property);\\n}\\n\\nregisterDependencies(abortOnPropertyRead, abortOnRead);\\n\\nexports[\\\"abort-on-property-read\\\"] = makeInjector(abortOnPropertyRead);\\n\\n/**\\n * Patches a property on the `context` object to abort execution when the\\n * property is read.\\n *\\n * @param {string} loggingPrefix A string with which we prefix the logs.\\n * @param {Window} context The window object whose property we patch.\\n * @param {string} property The name of the property.\\n * @private\\n */\\nfunction abortOnRead(loggingPrefix, context, property)\\n{\\n  let debugLog = (debug ? log : () => {}).bind(null, loggingPrefix);\\n\\n  if (!property)\\n  {\\n    debugLog(\\\"no property to abort on read\\\");\\n    return;\\n  }\\n\\n  let rid = randomId();\\n\\n  function abort()\\n  {\\n    debugLog(`${property} access aborted`);\\n    throw new ReferenceError(rid);\\n  }\\n\\n  debugLog(`aborting on ${property} access`);\\n\\n  wrapPropertyAccess(context, property, {get: abort, set() {}});\\n  overrideOnError(rid);\\n}\\n\\nregisterDependencies(abortOnRead,\\n                     log,\\n                     overrideOnError,\\n                     randomId,\\n                     wrapPropertyAccess);\\n\\n/**\\n * Patches a property on the window object to abort execution when the\\n * property is written.\\n *\\n * No error is printed to the console.\\n *\\n * The idea originates from\\n * [uBlock Origin](https://github.com/uBlockOrigin/uAssets/blob/80b195436f8f8d78ba713237bfc268ecfc9d9d2b/filters/resources.txt#L1671).\\n * @alias module:content/snippets.abort-on-property-write\\n *\\n * @param {string} property The name of the property.\\n *\\n * @since Adblock Plus 3.4.3\\n */\\nfunction abortOnPropertyWrite(property)\\n{\\n  abortOnWrite(\\\"abort-on-property-write\\\", window, property);\\n}\\n\\nregisterDependencies(abortOnPropertyWrite, abortOnWrite);\\n\\nexports[\\\"abort-on-property-write\\\"] = makeInjector(abortOnPropertyWrite);\\n\\n/**\\n * Patches a property on the `context` object to abort execution when the\\n * property is written.\\n *\\n * @param {string} loggingPrefix A string with which we prefix the logs.\\n * @param {Window} context The window object whose property we patch.\\n * @param {string} property The name of the property.\\n * @private\\n */\\nfunction abortOnWrite(loggingPrefix, context, property)\\n{\\n  let debugLog = (debug ? log : () => {}).bind(null, loggingPrefix);\\n\\n  if (!property)\\n  {\\n    debugLog(\\\"no property to abort on write\\\");\\n    return;\\n  }\\n\\n  let rid = randomId();\\n\\n  function abort()\\n  {\\n    debugLog(`setting ${property} aborted`);\\n    throw new ReferenceError(rid);\\n  }\\n\\n  debugLog(`aborting when setting ${property}`);\\n\\n  wrapPropertyAccess(context, property, {set: abort});\\n  overrideOnError(rid);\\n}\\n\\nregisterDependencies(abortOnWrite,\\n                     log,\\n                     overrideOnError,\\n                     randomId,\\n                     wrapPropertyAccess);\\n\\n/**\\n * Aborts the execution of an inline script.\\n * @alias module:content/snippets.abort-current-inline-script\\n *\\n * @param {string} api API function or property name to anchor on.\\n * @param {?string} [search] If specified, only scripts containing the given\\n *   string are prevented from executing. If the string begins and ends with a\\n *   slash (`/`), the text in between is treated as a regular expression.\\n *\\n * @since Adblock Plus 3.4.3\\n */\\nfunction abortCurrentInlineScript(api, search = null)\\n{\\n  let re = search ? toRegExp(search) : null;\\n\\n  let rid = randomId();\\n  let us = document.currentScript;\\n\\n  let object = window;\\n  let path = api.split(\\\".\\\");\\n  let name = path.pop();\\n\\n  for (let node of path)\\n  {\\n    object = object[node];\\n\\n    if (!object || !(typeof object == \\\"object\\\" || typeof object == \\\"function\\\"))\\n      return;\\n  }\\n\\n  let {get: prevGetter, set: prevSetter} =\\n    Object.getOwnPropertyDescriptor(object, name) || {};\\n\\n  let currentValue = object[name];\\n\\n  let abort = () =>\\n  {\\n    let element = document.currentScript;\\n    if (element instanceof HTMLScriptElement && element.src == \\\"\\\" &&\\n        element != us && (!re || re.test(element.textContent)))\\n      throw new ReferenceError(rid);\\n  };\\n\\n  let descriptor = {\\n    get()\\n    {\\n      abort();\\n\\n      if (prevGetter)\\n        return prevGetter.call(this);\\n\\n      return currentValue;\\n    },\\n    set(value)\\n    {\\n      abort();\\n\\n      if (prevSetter)\\n        prevSetter.call(this, value);\\n      else\\n        currentValue = value;\\n    }\\n  };\\n\\n  wrapPropertyAccess(object, name, descriptor);\\n\\n  overrideOnError(rid);\\n}\\n\\nregisterDependencies(abortCurrentInlineScript,\\n                     overrideOnError,\\n                     randomId,\\n                     toRegExp,\\n                     wrapPropertyAccess);\\n\\nexports[\\\"abort-current-inline-script\\\"] =\\n  makeInjector(abortCurrentInlineScript);\\n\\n\\n/**\\n * Traps calls to JSON.parse, and if the result of the parsing is an Object, it\\n * will remove specified properties from the result before returning to the\\n * caller.\\n *\\n * The idea originates from\\n * [uBlock Origin](https://github.com/gorhill/uBlock/commit/2fd86a66).\\n * @alias module:content/snippets.json-prune\\n *\\n * @param {string} rawPrunePaths A list of space-separated properties to remove.\\n * @param {?string} [rawNeedlePaths] A list of space-separated properties which\\n *   must be all present for the pruning to occur.\\n *\\n * @since Adblock Plus 3.9.0\\n */\\nfunction jsonPrune(rawPrunePaths, rawNeedlePaths = \\\"\\\")\\n{\\n  if (!rawPrunePaths)\\n    throw new Error(\\\"Missing paths to prune\\\");\\n  let prunePaths = rawPrunePaths.split(/ +/);\\n  let needlePaths = rawNeedlePaths !== \\\"\\\" ? rawNeedlePaths.split(/ +/) : [];\\n  let currentValue = JSON.parse;\\n\\n  let descriptor = {\\n    value(...args)\\n    {\\n      let result;\\n      result = currentValue.apply(this, args);\\n\\n      if (needlePaths.length > 0 &&\\n          needlePaths.some(path => !findOwner(result, path)))\\n        return result;\\n\\n      for (let path of prunePaths)\\n      {\\n        let details = findOwner(result, path);\\n        if (typeof details != \\\"undefined\\\")\\n          delete details[0][details[1]];\\n      }\\n      return result;\\n    }\\n  };\\n\\n  Object.defineProperty(JSON, \\\"parse\\\", descriptor);\\n\\n  function findOwner(root, path)\\n  {\\n    if (!(root instanceof window.Object))\\n      return;\\n\\n    let object = root;\\n    let chain = path.split(\\\".\\\");\\n\\n    if (chain.length === 0)\\n      return;\\n\\n    for (let i = 0; i < chain.length - 1; i++)\\n    {\\n      let prop = chain[i];\\n      if (!utils.isOwnProperty(object, prop))\\n        return;\\n\\n      object = object[prop];\\n\\n      if (!(object instanceof window.Object))\\n        return;\\n    }\\n\\n    let prop = chain[chain.length - 1];\\n    if (utils.isOwnProperty(object, prop))\\n      return [object, prop];\\n  }\\n}\\n\\nexports[\\\"json-prune\\\"] = makeInjector(jsonPrune);\\n\\n/**\\n * Freezes a DOM element so it prevents adding new nodes inside it.\\n * @alias module:content/snippets.freeze-element\\n *\\n * @param {string} selector The CSS selector for the parent element that\\n *   we want to freeze\\n * @param {string?} [options] A single parameter for snippet's options.\\n *   A string containing all the options we want to pass, each of them\\n *   separated by a plus character (`+`). Empty single quotes if none (`''`).\\n *   Available options:\\n *   **subtree** (if we want to freeze all the element's children as well);\\n *   **abort** (throw an error every time an child element gets added);\\n * @param {string?} [exceptions] An array of regex/selectors used to specify\\n *   the nodes we don't want to prevent being added.\\n *   Each array item can be:\\n *   **selector** (targeting Element nodes);\\n *   **regex** (targeting Text nodes, identified by slash);\\n *\\n * @since Adblock Plus 3.10\\n */\\nfunction freezeElement(selector, options = \\\"\\\", ...exceptions)\\n{\\n  let observer;\\n  let subtree = false;\\n  let shouldAbort = false;\\n  let exceptionSelectors = exceptions.filter(e => !isRegex(e));\\n  let regexExceptions = exceptions.filter(isRegex).map(toRegExp);\\n  let rid = randomId();\\n  let targetNodes;\\n\\n  checkOptions();\\n  let data = {\\n    selector,\\n    shouldAbort,\\n    rid,\\n    exceptionSelectors,\\n    regexExceptions,\\n    changeId: 0\\n  };\\n  if (!ABP.DOM.frozen.has(document))\\n  {\\n    ABP.DOM.frozen.set(document, true);\\n    proxyNativeProperties();\\n  }\\n  observer = new MutationObserver(searchAndAttach);\\n  observer.observe(document, {childList: true, subtree: true});\\n  searchAndAttach();\\n\\n  function isRegex(s)\\n  {\\n    return s.length >= 2 && s[0] == \\\"/\\\" && s[s.length - 1] == \\\"/\\\";\\n  }\\n\\n  function checkOptions()\\n  {\\n    let optionsChunks = options.split(\\\"+\\\");\\n    if (optionsChunks.length === 1 && optionsChunks[0] === \\\"\\\")\\n      optionsChunks = [];\\n    for (let chunk of optionsChunks)\\n    {\\n      switch (chunk)\\n      {\\n        case \\\"subtree\\\":\\n          subtree = true;\\n          break;\\n        case \\\"abort\\\":\\n          shouldAbort = true;\\n          break;\\n        default:\\n          throw new Error(\\\"[freeze] Unknown option passed to the snippet.\\\" +\\n                          \\\" [selector]: \\\" + selector +\\n                          \\\" [option]: \\\" + chunk);\\n      }\\n    }\\n  }\\n\\n  function proxyNativeProperties()\\n  {\\n    let descriptor;\\n\\n    descriptor = getAppendChildDescriptor(Node.prototype, \\\"appendChild\\\",\\n                                          isFrozen, getSnippetData);\\n    wrapPropertyAccess(Node.prototype, \\\"appendChild\\\", descriptor);\\n\\n    descriptor = getAppendChildDescriptor(Node.prototype, \\\"insertBefore\\\",\\n                                          isFrozen, getSnippetData);\\n    wrapPropertyAccess(Node.prototype, \\\"insertBefore\\\", descriptor);\\n\\n    descriptor = getAppendChildDescriptor(Node.prototype, \\\"replaceChild\\\",\\n                                          isFrozen, getSnippetData);\\n    wrapPropertyAccess(Node.prototype, \\\"replaceChild\\\", descriptor);\\n\\n    descriptor = getAppendDescriptor(Element.prototype, \\\"append\\\",\\n                                     isFrozen, getSnippetData);\\n    wrapPropertyAccess(Element.prototype, \\\"append\\\", descriptor);\\n\\n    descriptor = getAppendDescriptor(Element.prototype, \\\"prepend\\\",\\n                                     isFrozen, getSnippetData);\\n    wrapPropertyAccess(Element.prototype, \\\"prepend\\\", descriptor);\\n\\n    descriptor = getAppendDescriptor(Element.prototype, \\\"replaceWith\\\",\\n                                     isFrozenOrHasFrozenParent,\\n                                     getSnippetDataFromNodeOrParent);\\n    wrapPropertyAccess(Element.prototype, \\\"replaceWith\\\", descriptor);\\n\\n    descriptor = getAppendDescriptor(Element.prototype, \\\"after\\\",\\n                                     isFrozenOrHasFrozenParent,\\n                                     getSnippetDataFromNodeOrParent);\\n    wrapPropertyAccess(Element.prototype, \\\"after\\\", descriptor);\\n\\n    descriptor = getAppendDescriptor(Element.prototype, \\\"before\\\",\\n                                     isFrozenOrHasFrozenParent,\\n                                     getSnippetDataFromNodeOrParent);\\n    wrapPropertyAccess(Element.prototype, \\\"before\\\", descriptor);\\n\\n    descriptor = getInsertAdjacentDescriptor(Element.prototype,\\n                                             \\\"insertAdjacentElement\\\",\\n                                             isFrozenAndInsideTarget,\\n                                             getSnippetDataBasedOnTarget);\\n    wrapPropertyAccess(Element.prototype, \\\"insertAdjacentElement\\\", descriptor);\\n\\n    descriptor = getInsertAdjacentDescriptor(Element.prototype,\\n                                             \\\"insertAdjacentHTML\\\",\\n                                             isFrozenAndInsideTarget,\\n                                             getSnippetDataBasedOnTarget);\\n    wrapPropertyAccess(Element.prototype, \\\"insertAdjacentHTML\\\", descriptor);\\n\\n    descriptor = getInsertAdjacentDescriptor(Element.prototype,\\n                                             \\\"insertAdjacentText\\\",\\n                                             isFrozenAndInsideTarget,\\n                                             getSnippetDataBasedOnTarget);\\n    wrapPropertyAccess(Element.prototype, \\\"insertAdjacentText\\\", descriptor);\\n\\n    descriptor = getInnerHTMLDescriptor(Element.prototype, \\\"innerHTML\\\",\\n                                        isFrozen, getSnippetData);\\n    wrapPropertyAccess(Element.prototype, \\\"innerHTML\\\", descriptor);\\n\\n    descriptor = getInnerHTMLDescriptor(Element.prototype, \\\"outerHTML\\\",\\n                                        isFrozenOrHasFrozenParent,\\n                                        getSnippetDataFromNodeOrParent);\\n    wrapPropertyAccess(Element.prototype, \\\"outerHTML\\\", descriptor);\\n\\n    descriptor = getTextContentDescriptor(Node.prototype, \\\"textContent\\\",\\n                                          isFrozen, getSnippetData);\\n    wrapPropertyAccess(Node.prototype, \\\"textContent\\\", descriptor);\\n\\n    descriptor = getTextContentDescriptor(HTMLElement.prototype, \\\"innerText\\\",\\n                                          isFrozen, getSnippetData);\\n    wrapPropertyAccess(HTMLElement.prototype, \\\"innerText\\\", descriptor);\\n\\n    descriptor = getTextContentDescriptor(Node.prototype, \\\"nodeValue\\\",\\n                                          isFrozen, getSnippetData);\\n    wrapPropertyAccess(Node.prototype, \\\"nodeValue\\\", descriptor);\\n\\n    function isFrozen(node)\\n    {\\n      return node && ABP.DOM.frozen.has(node);\\n    }\\n\\n    function isFrozenOrHasFrozenParent(node)\\n    {\\n      try\\n      {\\n        return node &&\\n               (ABP.DOM.frozen.has(node) ||\\n               ABP.DOM.frozen.has(DOM.getParentNode.call(node)));\\n      }\\n      catch (error)\\n      {\\n        return false;\\n      }\\n    }\\n\\n    function isFrozenAndInsideTarget(node, isInsideTarget)\\n    {\\n      try\\n      {\\n        return node &&\\n               (ABP.DOM.frozen.has(node) && isInsideTarget ||\\n                ABP.DOM.frozen.has(DOM.getParentNode.call(node)) &&\\n                !isInsideTarget);\\n      }\\n      catch (error)\\n      {\\n        return false;\\n      }\\n    }\\n\\n    function getSnippetData(node)\\n    {\\n      return ABP.DOM.frozen.get(node);\\n    }\\n\\n    function getSnippetDataFromNodeOrParent(node)\\n    {\\n      try\\n      {\\n        if (ABP.DOM.frozen.has(node))\\n          return ABP.DOM.frozen.get(node);\\n        let parent = DOM.getParentNode.call(node);\\n        return ABP.DOM.frozen.get(parent);\\n      }\\n      catch (error) {}\\n    }\\n\\n    function getSnippetDataBasedOnTarget(node, isInsideTarget)\\n    {\\n      try\\n      {\\n        if (ABP.DOM.frozen.has(node) && isInsideTarget)\\n          return ABP.DOM.frozen.get(node);\\n        let parent = DOM.getParentNode.call(node);\\n        return ABP.DOM.frozen.get(parent);\\n      }\\n      catch (error) {}\\n    }\\n  }\\n\\n  function searchAndAttach()\\n  {\\n    targetNodes = ABP.DOM.document.$$(selector);\\n    markNodes(targetNodes, false);\\n  }\\n\\n  function markNodes(nodes, isChild = true)\\n  {\\n    for (let node of nodes)\\n    {\\n      if (!ABP.DOM.frozen.has(node))\\n      {\\n        ABP.DOM.frozen.set(node, data);\\n        if (!isChild && subtree)\\n        {\\n          new MutationObserver(mutationsList =>\\n          {\\n            for (let mutation of mutationsList)\\n              markNodes(mutation.addedNodes);\\n          }).observe(node, {childList: true, subtree: true});\\n        }\\n        if (subtree && node.nodeType === Node.ELEMENT_NODE)\\n          markNodes(node.childNodes);\\n      }\\n    }\\n  }\\n\\n  // utilities\\n  function logPrefixed(id, ...args)\\n  {\\n    log(`[freeze][${id}] `, ...args);\\n  }\\n\\n  function logChange(nodeOrDOMString, target, property, snippetData)\\n  {\\n    let targetSelector = snippetData.selector;\\n    let chgId = snippetData.changeId;\\n    let isDOMString = typeof nodeOrDOMString == \\\"string\\\";\\n    let action = snippetData.shouldAbort ? \\\"aborting\\\" : \\\"watching\\\";\\n    console.groupCollapsed(`[freeze][${chgId}] ${action}: ${targetSelector}`);\\n    switch (property)\\n    {\\n      case \\\"appendChild\\\":\\n      case \\\"append\\\":\\n      case \\\"prepend\\\":\\n      case \\\"insertBefore\\\":\\n      case \\\"replaceChild\\\":\\n      case \\\"insertAdjacentElement\\\":\\n      case \\\"insertAdjacentHTML\\\":\\n      case \\\"insertAdjacentText\\\":\\n      case \\\"innerHTML\\\":\\n      case \\\"outerHTML\\\":\\n        logPrefixed(chgId,\\n                    isDOMString ? \\\"text: \\\" : \\\"node: \\\", nodeOrDOMString);\\n        logPrefixed(chgId, \\\"added to node: \\\", target);\\n        break;\\n      case \\\"replaceWith\\\":\\n      case \\\"after\\\":\\n      case \\\"before\\\":\\n        logPrefixed(chgId,\\n                    isDOMString ? \\\"text: \\\" : \\\"node: \\\", nodeOrDOMString);\\n        logPrefixed(chgId, \\\"added to node: \\\", target.parentNode);\\n        break;\\n      case \\\"textContent\\\":\\n      case \\\"innerText\\\":\\n      case \\\"nodeValue\\\":\\n        logPrefixed(chgId, \\\"content of node: \\\", target);\\n        logPrefixed(chgId, \\\"changed to: \\\", nodeOrDOMString);\\n        break;\\n      default:\\n        break;\\n    }\\n    logPrefixed(chgId, `using the function \\\"${property}\\\"`);\\n    console.groupEnd();\\n    snippetData.changeId++;\\n  }\\n\\n  function isExceptionNode(element, expSelectors)\\n  {\\n    if (expSelectors)\\n    {\\n      for (let exception of expSelectors)\\n      {\\n        if (ABP.DOM.matches(element, exception))\\n          return true;\\n      }\\n    }\\n    return false;\\n  }\\n\\n  function isExceptionText(string, regExceptions)\\n  {\\n    if (regExceptions)\\n    {\\n      for (let exception of regExceptions)\\n      {\\n        if (exception.test(string))\\n          return true;\\n      }\\n    }\\n    return false;\\n  }\\n\\n  function abort(id)\\n  {\\n    throw new ReferenceError(id);\\n  }\\n\\n  // check inserted content\\n  function checkHTML(htmlText, parent, property, snippetData)\\n  {\\n    let finalValue = \\\"\\\";\\n    let domparser = new DOMParser();\\n    let doc = domparser.parseFromString(htmlText, \\\"text/html\\\");\\n    let nodes = doc.body.childNodes;\\n    let accepted = checkMultiple(nodes, parent, property, snippetData);\\n    let content = accepted.map(node =>\\n    {\\n      switch (node.nodeType)\\n      {\\n        case Node.ELEMENT_NODE:\\n          return node.outerHTML;\\n        case Node.TEXT_NODE:\\n          return node.textContent;\\n        default:\\n          return \\\"\\\";\\n      }\\n    });\\n    finalValue = content.join(\\\"\\\");\\n    return finalValue;\\n  }\\n\\n  function checkMultiple(nodesOrDOMStrings, parent, property, snippetData)\\n  {\\n    let accepted = [];\\n    for (let nodeOrDOMString of nodesOrDOMStrings)\\n    {\\n      if (checkShouldInsert(nodeOrDOMString, parent, property, snippetData))\\n        accepted.push(nodeOrDOMString);\\n    }\\n    return accepted;\\n  }\\n\\n  function checkShouldInsert(nodeOrDOMString, parent, property, snippetData)\\n  {\\n    let aborting = snippetData.shouldAbort;\\n    let regExceptions = snippetData.regexExceptions;\\n    let expSelectors = snippetData.exceptionSelectors;\\n    let id = snippetData.rid;\\n    if (typeof nodeOrDOMString == \\\"string\\\")\\n    {\\n      let domString = nodeOrDOMString;\\n      if (isExceptionText(domString, regExceptions))\\n        return true;\\n      if (debug)\\n        logChange(domString, parent, property, snippetData);\\n      if (aborting)\\n        abort(id);\\n      return debug;\\n    }\\n\\n    let node = nodeOrDOMString;\\n    switch (node.nodeType)\\n    {\\n      case Node.ELEMENT_NODE:\\n        if (isExceptionNode(node, expSelectors))\\n          return true;\\n        if (aborting)\\n        {\\n          if (debug)\\n            logChange(node, parent, property, snippetData);\\n          abort(id);\\n        }\\n        if (debug)\\n        {\\n          hideElement(node);\\n          logChange(node, parent, property, snippetData);\\n          return true;\\n        }\\n        return false;\\n      case Node.TEXT_NODE:\\n        if (isExceptionText(node.textContent, regExceptions))\\n          return true;\\n        if (debug)\\n          logChange(node, parent, property, snippetData);\\n        if (aborting)\\n          abort(id);\\n        return false;\\n      default:\\n        return true;\\n    }\\n  }\\n\\n  // descriptors\\n  function getAppendChildDescriptor(target, property, shouldValidate,\\n                                    getSnippetData)\\n  {\\n    let desc = Object.getOwnPropertyDescriptor(target, property) || {};\\n    let origin = desc.get && desc.get.call(target) || desc.value;\\n    if (!origin)\\n      return;\\n\\n    return {\\n      get()\\n      {\\n        return function(...args)\\n        {\\n          if (shouldValidate(this))\\n          {\\n            let snippetData = getSnippetData(this);\\n            if (snippetData)\\n            {\\n              let incomingNode = args[0];\\n              if (!checkShouldInsert(incomingNode, this, property, snippetData))\\n                return incomingNode;\\n            }\\n          }\\n          return origin.apply(this, args);\\n        };\\n      }\\n    };\\n  }\\n\\n  function getAppendDescriptor(target, property, shouldValidate, getSnippetData)\\n  {\\n    let desc = Object.getOwnPropertyDescriptor(target, property) || {};\\n    let origin = desc.get && desc.get.call(target) || desc.value;\\n    if (!origin)\\n      return;\\n    return {\\n      get()\\n      {\\n        return function(...nodesOrDOMStrings)\\n        {\\n          if (!shouldValidate(this))\\n            return origin.apply(this, nodesOrDOMStrings);\\n\\n          let snippetData = getSnippetData(this);\\n          if (!snippetData)\\n            return origin.apply(this, nodesOrDOMStrings);\\n\\n          let accepted = checkMultiple(nodesOrDOMStrings, this, property,\\n                                       snippetData);\\n          if (accepted.length > 0)\\n            return origin.apply(this, accepted);\\n        };\\n      }\\n    };\\n  }\\n\\n  function getInsertAdjacentDescriptor(target, property, shouldValidate,\\n                                       getSnippetData)\\n  {\\n    let desc = Object.getOwnPropertyDescriptor(target, property) || {};\\n    let origin = desc.get && desc.get.call(target) || desc.value;\\n    if (!origin)\\n      return;\\n\\n    return {\\n      get()\\n      {\\n        return function(...args)\\n        {\\n          let [position, value] = args;\\n          let isInsideTarget =\\n              position === \\\"afterbegin\\\" || position === \\\"beforeend\\\";\\n          if (shouldValidate(this, isInsideTarget))\\n          {\\n            let snippetData = getSnippetData(this, isInsideTarget);\\n            if (snippetData)\\n            {\\n              let parent = isInsideTarget ?\\n                           this :\\n                           DOM.getParentNode.call(this);\\n              let finalValue;\\n              switch (property)\\n              {\\n                case \\\"insertAdjacentElement\\\":\\n                  if (!checkShouldInsert(value, parent, property, snippetData))\\n                    return value;\\n                  break;\\n\\n                case \\\"insertAdjacentHTML\\\":\\n                  finalValue = checkHTML(value, parent, property, snippetData);\\n                  if (finalValue)\\n                    return origin.call(this, position, finalValue);\\n\\n                  return;\\n\\n                case \\\"insertAdjacentText\\\":\\n                  if (!checkShouldInsert(value, parent, property, snippetData))\\n                    return;\\n                  break;\\n\\n                default:\\n                  break;\\n              }\\n            }\\n          }\\n          return origin.apply(this, args);\\n        };\\n      }\\n    };\\n  }\\n\\n  function getInnerHTMLDescriptor(target, property, shouldValidate,\\n                                  getSnippetData)\\n  {\\n    let desc = Object.getOwnPropertyDescriptor(target, property) || {};\\n    let {set: prevSetter} = desc;\\n    if (!prevSetter)\\n      return;\\n\\n    return {\\n      set(htmlText)\\n      {\\n        if (!shouldValidate(this))\\n          return prevSetter.call(this, htmlText);\\n\\n        let snippetData = getSnippetData(this);\\n        if (!snippetData)\\n          return prevSetter.call(this, htmlText);\\n        let finalValue = checkHTML(htmlText, this, property, snippetData);\\n        if (finalValue)\\n          return prevSetter.call(this, finalValue);\\n      }\\n    };\\n  }\\n\\n  function getTextContentDescriptor(target, property, shouldValidate,\\n                                    getSnippetData)\\n  {\\n    let desc = Object.getOwnPropertyDescriptor(target, property) || {};\\n    let {set: prevSetter} = desc;\\n    if (!prevSetter)\\n      return;\\n\\n    return {\\n      set(domString)\\n      {\\n        if (!shouldValidate(this))\\n          return prevSetter.call(this, domString);\\n\\n        let snippetData = getSnippetData(this);\\n        if (!snippetData)\\n          return prevSetter.call(this, domString);\\n        if (checkShouldInsert(domString, this, property, snippetData))\\n          return prevSetter.call(this, domString);\\n      }\\n    };\\n  }\\n}\\n\\nregisterDependencies(freezeElement,\\n                     hideElement,\\n                     overrideOnError,\\n                     randomId,\\n                     toRegExp,\\n                     log,\\n                     wrapPropertyAccess);\\n\\nexports[\\\"freeze-element\\\"] = makeInjector(freezeElement);\\n\\n\\n/**\\n * Overrides a property's value on the window object with a set of\\n * available properties.\\n *\\n * Possible values to override the property with:\\n *   undefined\\n *   false\\n *   true\\n *   null\\n *   noopFunc   - function with empty body\\n *   trueFunc   - function returning true\\n *   falseFunc  - function returning false\\n *   ''         - empty string\\n *   positive decimal integer, no sign, with maximum value of 0x7FFF\\n *\\n * The idea originates from\\n * [uBlock Origin](https://github.com/uBlockOrigin/uAssets/blob/80b195436f8f8d78ba713237bfc268ecfc9d9d2b/filters/resources.txt#L2105).\\n * @alias module:content/snippets.override-property-read\\n *\\n * @param {string} property The name of the property.\\n * @param {string} value The value to override the property with.\\n *\\n * @since Adblock Plus 3.9.4\\n */\\nfunction overridePropertyRead(property, value)\\n{\\n  if (!property)\\n  {\\n    throw new Error(\\\"[override-property-read snippet]: \\\" +\\n                    \\\"No property to override.\\\");\\n  }\\n  if (typeof value === \\\"undefined\\\")\\n  {\\n    throw new Error(\\\"[override-property-read snippet]: \\\" +\\n                    \\\"No value to override with.\\\");\\n  }\\n\\n  let cValue;\\n  let debugLog = (debug ? log : () => {})\\n    .bind(null, \\\"override-property-read\\\");\\n\\n  if (value === \\\"false\\\")\\n  {\\n    cValue = false;\\n  }\\n  else if (value === \\\"true\\\")\\n  {\\n    cValue = true;\\n  }\\n  else if (value === \\\"null\\\")\\n  {\\n    cValue = null;\\n  }\\n  else if (value === \\\"noopFunc\\\")\\n  {\\n    cValue = () => {};\\n  }\\n  else if (value === \\\"trueFunc\\\")\\n  {\\n    cValue = () => true;\\n  }\\n  else if (value === \\\"falseFunc\\\")\\n  {\\n    cValue = () => false;\\n  }\\n  else if (/^\\\\d+$/.test(value))\\n  {\\n    cValue = parseFloat(value);\\n  }\\n  else if (value === \\\"\\\")\\n  {\\n    cValue = value;\\n  }\\n  else if (value !== \\\"undefined\\\")\\n  {\\n    throw new Error(\\\"[override-property-read snippet]: \\\" +\\n                    `Value \\\"${value}\\\" is not valid.`);\\n  }\\n\\n  let newGetter = () =>\\n  {\\n    debugLog(`${property} override done.`);\\n    return cValue;\\n  };\\n\\n  debugLog(`Overriding ${property}.`);\\n\\n  wrapPropertyAccess(window, property, {get: newGetter, set() {}});\\n}\\n\\nregisterDependencies(overridePropertyRead,\\n                     log,\\n                     wrapPropertyAccess);\\n\\nexports[\\\"override-property-read\\\"] = makeInjector(overridePropertyRead);\\n\\n/**\\n * Patches a list of properties on the iframes' window object to abort execution\\n * when the property is read.\\n *\\n * No error is printed to the console.\\n * @alias module:content/snippets.abort-on-iframe-property-read\\n *\\n * @param {...string} properties The list with the properties.\\n *\\n * @since Adblock Plus 3.10.1\\n */\\nfunction abortOnIframePropertyRead(...properties)\\n{\\n  abortOnIframe(properties, true, false);\\n}\\n\\nregisterDependencies(abortOnIframePropertyRead, abortOnIframe);\\n\\nexports[\\\"abort-on-iframe-property-read\\\"] =\\n  makeInjector(abortOnIframePropertyRead);\\n\\n/**\\n * Patches a list of properties on the iframes' window object to abort execution\\n * when the property is written.\\n *\\n * No error is printed to the console.\\n * @alias module:content/snippets.abort-on-iframe-property-write\\n *\\n * @param {...string} properties The list with the properties.\\n *\\n * @since Adblock Plus 3.10.1\\n */\\nfunction abortOnIframePropertyWrite(...properties)\\n{\\n  abortOnIframe(properties, false, true);\\n}\\n\\nregisterDependencies(abortOnIframePropertyWrite, abortOnIframe);\\n\\nexports[\\\"abort-on-iframe-property-write\\\"] =\\n  makeInjector(abortOnIframePropertyWrite);\\n\\n\\n/**\\n * Patches a list of properties on the iframes' window object to abort execution\\n * when the property is read/written.\\n *\\n * @param {...string} properties The list with the properties.\\n * @param {boolean?} [abortRead=false] Should abort on read option.\\n * @param {boolean?} [abortWrite=false] Should abort on write option.\\n * @private\\n */\\nfunction abortOnIframe(properties, abortRead = false, abortWrite = false)\\n{\\n  let abortedIframes = ABP.DOM.abortedIframes;\\n  let iframePropertiesToAbort = ABP.DOM.iframePropertiesToAbort;\\n\\n  // add new properties-to-abort to all aborted iframes' WeakMaps\\n  for (let frame of Array.from(window.frames))\\n  {\\n    if (abortedIframes.has(frame))\\n    {\\n      for (let property of properties)\\n      {\\n        if (abortRead)\\n          abortedIframes.get(frame).read.add(property);\\n        if (abortWrite)\\n          abortedIframes.get(frame).write.add(property);\\n      }\\n    }\\n  }\\n\\n  // store properties-to-abort\\n  for (let property of properties)\\n  {\\n    if (abortRead)\\n      iframePropertiesToAbort.read.add(property);\\n    if (abortWrite)\\n      iframePropertiesToAbort.write.add(property);\\n  }\\n\\n  queryAndProxyIframe();\\n  if (!abortedIframes.has(document))\\n  {\\n    abortedIframes.set(document, true);\\n    addHooksOnDomAdditions(queryAndProxyIframe);\\n  }\\n\\n  function queryAndProxyIframe()\\n  {\\n    for (let frame of Array.from(window.frames))\\n    {\\n      // add WeakMap entry for every missing frame\\n      if (!abortedIframes.has(frame))\\n      {\\n        abortedIframes.set(frame, {\\n          read: new Set(iframePropertiesToAbort.read),\\n          write: new Set(iframePropertiesToAbort.write)\\n        });\\n      }\\n\\n      let readProps = abortedIframes.get(frame).read;\\n      if (readProps.size > 0)\\n      {\\n        let props = Array.from(readProps);\\n        readProps.clear();\\n        for (let property of props)\\n          abortOnRead(\\\"abort-on-iframe-property-read\\\", frame, property);\\n      }\\n\\n      let writeProps = abortedIframes.get(frame).write;\\n      if (writeProps.size > 0)\\n      {\\n        let props = Array.from(writeProps);\\n        writeProps.clear();\\n        for (let property of props)\\n          abortOnWrite(\\\"abort-on-iframe-property-write\\\", frame, property);\\n      }\\n    }\\n  }\\n}\\n\\nregisterDependencies(abortOnIframe,\\n                     randomId,\\n                     addHooksOnDomAdditions,\\n                     abortOnRead,\\n                     abortOnWrite);\\n\\n/**\\n * Patches the native functions which are responsible with adding Nodes to DOM.\\n * Adds a hook at right after the addition.\\n *\\n * @param {function} endCallback The list with the properties.\\n * @private\\n */\\nfunction addHooksOnDomAdditions(endCallback)\\n{\\n  let descriptor;\\n\\n  wrapAccess(Node.prototype, [\\\"appendChild\\\", \\\"insertBefore\\\", \\\"replaceChild\\\"]);\\n  wrapAccess(Element.prototype, [\\\"append\\\", \\\"prepend\\\", \\\"replaceWith\\\", \\\"after\\\",\\n                                 \\\"before\\\", \\\"insertAdjacentElement\\\",\\n                                 \\\"insertAdjacentHTML\\\"]);\\n\\n  descriptor = getInnerHTMLDescriptor(Element.prototype, \\\"innerHTML\\\");\\n  wrapPropertyAccess(Element.prototype, \\\"innerHTML\\\", descriptor);\\n\\n  descriptor = getInnerHTMLDescriptor(Element.prototype, \\\"outerHTML\\\");\\n  wrapPropertyAccess(Element.prototype, \\\"outerHTML\\\", descriptor);\\n\\n  function wrapAccess(prototype, names)\\n  {\\n    for (let name of names)\\n    {\\n      let desc = getAppendChildDescriptor(prototype, name);\\n      wrapPropertyAccess(prototype, name, desc);\\n    }\\n  }\\n\\n  function getAppendChildDescriptor(target, property)\\n  {\\n    let currentValue = target[property];\\n    return {\\n      get()\\n      {\\n        return function(...args)\\n        {\\n          let result;\\n          result = currentValue.apply(this, args);\\n          endCallback && endCallback();\\n          return result;\\n        };\\n      }\\n    };\\n  }\\n\\n  function getInnerHTMLDescriptor(target, property)\\n  {\\n    let desc = Object.getOwnPropertyDescriptor(target, property);\\n    let {set: prevSetter} = desc || {};\\n    return {\\n      set(val)\\n      {\\n        let result;\\n        result = prevSetter.call(this, val);\\n        endCallback && endCallback();\\n        return result;\\n      }\\n    };\\n  }\\n}\\n\\nregisterDependencies(addHooksOnDomAdditions, wrapPropertyAccess);\\n\\n/**\\n * Strips a query string parameter from `fetch()` calls.\\n * @alias module:content/snippets.strip-fetch-query-parameter\\n *\\n * @param {string} name The name of the parameter.\\n * @param {?string} [urlPattern] An optional pattern that the URL must match.\\n *\\n * @since Adblock Plus 3.5.1\\n */\\nfunction stripFetchQueryParameter(name, urlPattern = null)\\n{\\n  let fetch_ = window.fetch;\\n  if (typeof fetch_ != \\\"function\\\")\\n    return;\\n\\n  let urlRegExp = urlPattern ? toRegExp(urlPattern) : null;\\n  window.fetch = function fetch(...args)\\n  {\\n    let [source] = args;\\n    if (typeof source == \\\"string\\\" &&\\n        (!urlRegExp || urlRegExp.test(source)))\\n    {\\n      let url = new URL(source);\\n      url.searchParams.delete(name);\\n      args[0] = url.href;\\n    }\\n\\n    return fetch_.apply(this, args);\\n  };\\n}\\n\\nregisterDependencies(stripFetchQueryParameter, toRegExp);\\n\\nexports[\\\"strip-fetch-query-parameter\\\"] =\\n  makeInjector(stripFetchQueryParameter);\\n\\n/**\\n * Represents an undirected graph. Used for producing adjacency and feature\\n * matrices.\\n */\\nclass UndirectedGraph\\n{\\n  /**\\n   * Initialize an empty graph of a predefined size\\n   * @param {number} numOfVertices - size of a new graph\\n   * @private\\n   */\\n  constructor(numOfVertices)\\n  {\\n    // Create adjacency matrix and initialize it with '0's\\n    let emptyMatrix = new Array(numOfVertices);\\n    for (let i = 0; i < numOfVertices; i++)\\n      emptyMatrix[i] = new Array(numOfVertices).fill(0);\\n    this.adjacencyMatrix = emptyMatrix;\\n  }\\n\\n  /**\\n   * Add an edge from node A to node B\\n   * @param {number} posA - number of a node from which to add an edge\\n   * @param {number} posB - number of a node into which to add an edge\\n   * @return {bool} whether operation was succesful\\n   * @private\\n   */\\n  addEdge(posA, posB)\\n  {\\n    if (posA < 0 || posB < 0)\\n      throw new Error(\\\"Can't add an Edge to vertex at negative position \\\");\\n\\n    let numOfVertices = this.adjacencyMatrix.length;\\n    if (posA >= numOfVertices || posB >= numOfVertices)\\n      return false;\\n    this.adjacencyMatrix[posA][posB] = 1;\\n    // We want to be symmetric, as this is undirected graph\\n    this.adjacencyMatrix[posB][posA] = 1;\\n    return true;\\n  }\\n}\\n\\n// Source: https://github.com/sindresorhus/html-tags/blob/master/html-tags.json\\nlet htmlTags = [\\n  \\\"a\\\", \\\"abbr\\\", \\\"address\\\", \\\"area\\\", \\\"article\\\", \\\"aside\\\", \\\"audio\\\",\\n  \\\"b\\\", \\\"base\\\", \\\"bdi\\\", \\\"bdo\\\", \\\"blockquote\\\", \\\"body\\\", \\\"br\\\", \\\"button\\\", \\\"canvas\\\",\\n  \\\"caption\\\", \\\"cite\\\", \\\"code\\\", \\\"col\\\", \\\"colgroup\\\", \\\"data\\\", \\\"datalist\\\", \\\"dd\\\", \\\"del\\\",\\n  \\\"details\\\", \\\"dfn\\\", \\\"dialog\\\", \\\"div\\\", \\\"dl\\\", \\\"dt\\\", \\\"em\\\", \\\"embed\\\", \\\"fieldset\\\",\\n  \\\"figcaption\\\", \\\"figure\\\", \\\"footer\\\", \\\"form\\\", \\\"h1\\\", \\\"h2\\\", \\\"h3\\\", \\\"h4\\\", \\\"h5\\\", \\\"h6\\\",\\n  \\\"head\\\", \\\"header\\\", \\\"hgroup\\\", \\\"hr\\\", \\\"html\\\", \\\"i\\\", \\\"iframe\\\", \\\"img\\\", \\\"input\\\",\\n  \\\"ins\\\", \\\"kbd\\\", \\\"keygen\\\", \\\"label\\\", \\\"legend\\\", \\\"li\\\", \\\"link\\\", \\\"main\\\", \\\"map\\\",\\n  \\\"mark\\\", \\\"math\\\", \\\"menu\\\", \\\"menuitem\\\", \\\"meta\\\", \\\"meter\\\", \\\"nav\\\", \\\"noscript\\\",\\n  \\\"object\\\", \\\"ol\\\", \\\"optgroup\\\", \\\"option\\\", \\\"output\\\", \\\"p\\\", \\\"param\\\", \\\"picture\\\",\\n  \\\"pre\\\", \\\"progress\\\", \\\"q\\\", \\\"rb\\\", \\\"rp\\\", \\\"rt\\\", \\\"rtc\\\", \\\"ruby\\\", \\\"s\\\", \\\"samp\\\",\\n  \\\"script\\\", \\\"section\\\", \\\"select\\\", \\\"slot\\\", \\\"small\\\", \\\"source\\\", \\\"span\\\", \\\"strong\\\",\\n  \\\"style\\\", \\\"sub\\\", \\\"summary\\\", \\\"sup\\\", \\\"svg\\\", \\\"table\\\", \\\"tbody\\\", \\\"td\\\", \\\"template\\\",\\n  \\\"textarea\\\", \\\"tfoot\\\", \\\"th\\\", \\\"thead\\\", \\\"time\\\", \\\"title\\\", \\\"tr\\\", \\\"track\\\", \\\"u\\\",\\n  \\\"ul\\\", \\\"var\\\", \\\"video\\\", \\\"wbr\\\"\\n];\\nconst GRAPH_CUT_OFF = 200;\\nconst THRESHOLD = 0.5;\\n\\n/**\\n* A (tag, int) Map of all HTML tags with values reflecting the number of a tag\\n* in alphabetical enumeration.\\n* @type {Map}\\n*/\\nlet htmlTagsMap = new Map(\\n  htmlTags.map((name, index) => [name.toUpperCase(), index + 1])\\n);\\n\\n\\n/**\\n * Builds an adjecency matrix and a feature matrix based on an input element.\\n * @param {Element} target Input element to convert.\\n * @param {string} tagName An HTML tag name to filter mutations.\\n * @returns {Tuple} (adjMatrix, elementTags) - a 2D array that represent an\\n * adjacency matrix of an element. HTML elements are undirected trees, so the\\n * adjacency matrix is symmetric.\\n * elementTags - a 1D feature matrix, where each element is represents a type\\n * of a node.\\n * @private\\n */\\nfunction processElement(target)\\n{\\n  let {mark, end} = profile(\\\"ml:addEdges\\\");\\n\\n  let elementGraph = new UndirectedGraph(GRAPH_CUT_OFF);\\n  let numOfElements = 1;\\n\\n  let elementTags = new Array(GRAPH_CUT_OFF).fill(0);\\n\\n  let addEdges = (parentElement, parentPos) =>\\n  {\\n    let children = parentElement.children;\\n    for (let child of children)\\n    {\\n      if (numOfElements > GRAPH_CUT_OFF)\\n        break;\\n\\n      elementTags[numOfElements] = htmlTagsMap.get(child.tagName) || 0;\\n      elementGraph.addEdge(parentPos, numOfElements);\\n      addEdges(child, numOfElements++);\\n    }\\n  };\\n\\n  elementTags[0] = (htmlTagsMap.get(target.tagName) || 0);\\n\\n  // Kick off recursive graph building\\n  mark();\\n  addEdges(target, 0);\\n  end();\\n  let adjMatrix = elementGraph.adjacencyMatrix;\\n  return {adjMatrix, elementTags};\\n}\\n\\n/**\\n * Runs a ML prediction on each element that matches a selector.\\n * @param {string} selector A selector to use for finding candidates.\\n * @param {WeakSet} seenMlTargets Matched elements to ignore.\\n *\\n * @private\\n */\\nfunction predictAds(selector, seenMlTargets)\\n{\\n  let debugLog = (debug ? log : () => {})\\n                  .bind(null, \\\"ml-hide-if-graph-matches\\\");\\n\\n  let targets = document.querySelectorAll(selector);\\n  for (let target of targets)\\n  {\\n    if (seenMlTargets.has(target))\\n      continue;\\n\\n    if (target.innerText == \\\"\\\")\\n      continue;\\n\\n    seenMlTargets.add(target);\\n    let processedElement = processElement(target);\\n\\n    // as this call is asynchronous, ensure the id is unique\\n    let {mark, end} = profile(`ml:inference:${randomId()}`);\\n    mark();\\n    browser.runtime.sendMessage({\\n      type: \\\"ml.inference\\\",\\n      inputs: [\\n        {\\n          data: [processedElement.elementTags], preprocess: [\\n            {funcName: \\\"cast\\\", args: \\\"int32\\\"},\\n            {funcName: \\\"pad\\\", args: GRAPH_CUT_OFF},\\n            {funcName: \\\"oneHot\\\", args: htmlTags.length},\\n            {funcName: \\\"cast\\\", args: \\\"float32\\\"}\\n          ]\\n        },\\n        {\\n          data: [processedElement.adjMatrix], preprocess: [\\n            {funcName: \\\"pad\\\", args: GRAPH_CUT_OFF},\\n            {funcName: \\\"unstack\\\"},\\n            {funcName: \\\"localPooling\\\"},\\n            {funcName: \\\"stack\\\"}\\n          ]\\n        }\\n      ],\\n      model: \\\"mlHideIfGraphMatches\\\"\\n    }).then(rawPrediction =>\\n    {\\n      end(true);\\n      debugLog(rawPrediction);\\n\\n      let predictionValues = Object.values(rawPrediction);\\n\\n      if (!predictionValues.some(value => value > 0))\\n        throw new Error(\\\"ML prediction results are corrupted\\\");\\n\\n      let result = predictionValues.filter((element, index) =>\\n        index % 2 == 0\\n      ).map(element => element > THRESHOLD);\\n\\n      if (!result[0])\\n      {\\n        debugLog(\\\"Detected ad: \\\" + target.innerText);\\n        hideElement(target);\\n      }\\n    }).catch(() =>\\n    {\\n      // ensure the metric is sent even on possible ML failures\\n      end(true);\\n    });\\n  }\\n}\\n\\n/**\\n * Hides any HTML element if its structure (graph) is classified as an ad\\n * by a built-in machine learning model.\\n * @alias module:content/snippets.ml-hide-if-graph-matches\\n *\\n * @param {string} selector A selector that produces a list of targets to\\n * classify.\\n * @param {string} tagName An HTML tag name to filter mutations.\\n *\\n * @since Adblock Plus 3.8\\n */\\nfunction mlHideIfGraphMatches(selector, tagName)\\n{\\n  let scheduled = false;\\n  let seenMlTargets = new WeakSet();\\n\\n  if (typeof browser === \\\"undefined\\\")\\n    self.browser = chrome;\\n\\n  let relevantTagName = tagName;\\n  let callback = mutations =>\\n  {\\n    for (let mutation of mutations)\\n    {\\n      if (mutation.target.tagName == relevantTagName)\\n      {\\n        if (!scheduled)\\n        {\\n          scheduled = true;\\n          requestAnimationFrame(() =>\\n          {\\n            scheduled = false;\\n            predictAds(selector, seenMlTargets);\\n          });\\n        }\\n        break;\\n      }\\n    }\\n  };\\n\\n  new MutationObserver(callback)\\n    .observe(document, {childList: true, characterData: true, subtree: true});\\n\\n  predictAds(selector, seenMlTargets);\\n}\\n\\nexports[\\\"ml-hide-if-graph-matches\\\"] = mlHideIfGraphMatches;\\n\\n/**\\n * Calculates and returns the perceptual hash of the supplied image.\\n *\\n * The following lines are based off the blockhash-js library which is\\n * licensed under the MIT licence\\n * {@link https://github.com/commonsmachinery/blockhash-js/tree/2084417e40005e37f4ad957dbd2bca08ddc222bc Blockhash.js}\\n *\\n * @param {object} imageData ImageData object containing the image data of the\\n *  image for which a hash should be calculated\\n * @param {?number} [blockBits] The block width used to generate the perceptual\\n *   image hash, a number of 4 will split the image into 16 blocks\\n *   (width/4 * height/4). Defaults to 8.\\n * @returns {string} The resulting hash\\n * @private\\n */\\nfunction hashImage(imageData, blockBits)\\n{\\n  function median(mdarr)\\n  {\\n    mdarr.sort((a, b) => a - b);\\n    let {length} = mdarr;\\n    if (length % 2 === 0)\\n      return (mdarr[length / 2 - 1] + mdarr[length / 2]) / 2.0;\\n    return mdarr[(length / 2) | 0];\\n  }\\n\\n  function translateBlocksToBits(blocks, pixelsPerBlock)\\n  {\\n    let halfBlockValue = pixelsPerBlock * 256 * 3 / 2;\\n    let bandsize = blocks.length / 4;\\n\\n    // Compare medians across four horizontal bands\\n    for (let i = 0; i < 4; i++)\\n    {\\n      let index = i * bandsize;\\n      let length = (i + 1) * bandsize;\\n      let m = median(blocks.slice(index, length));\\n      for (let j = index; j < length; j++)\\n      {\\n        let v = blocks[j];\\n\\n        // Output a 1 if the block is brighter than the median.\\n        // With images dominated by black or white, the median may\\n        // end up being 0 or the max value, and thus having a lot\\n        // of blocks of value equal to the median.  To avoid\\n        // generating hashes of all zeros or ones, in that case output\\n        // 0 if the median is in the lower value space, 1 otherwise\\n        blocks[j] = (v > m || (m - v < 1 && m > halfBlockValue)) ? 1 : 0;\\n      }\\n    }\\n  }\\n\\n  function bitsToHexhash(bitsArray)\\n  {\\n    let hex = [];\\n    let {length} = bitsArray;\\n    for (let i = 0; i < length; i += 4)\\n    {\\n      let nibble = bitsArray.slice(i, i + 4);\\n      hex.push(parseInt(nibble.join(\\\"\\\"), 2).toString(16));\\n    }\\n\\n    return hex.join(\\\"\\\");\\n  }\\n\\n  function bmvbhashEven(data, bits)\\n  {\\n    let {width, height, data: imgData} = data;\\n    let blocksizeX = (width / bits) | 0;\\n    let blocksizeY = (height / bits) | 0;\\n\\n    let result = new Array(bits * bits);\\n\\n    for (let y = 0; y < bits; y++)\\n    {\\n      for (let x = 0; x < bits; x++)\\n      {\\n        let total = 0;\\n\\n        for (let iy = 0; iy < blocksizeY; iy++)\\n        {\\n          let ii = ((y * blocksizeY + iy) * width + x * blocksizeX) * 4;\\n\\n          for (let ix = 0; ix < blocksizeX; ix++)\\n          {\\n            let alpha = imgData[ii + 3];\\n            if (alpha === 0)\\n              total += 765;\\n            else\\n              total += imgData[ii] + imgData[ii + 1] + imgData[ii + 2];\\n\\n            ii += 4;\\n          }\\n        }\\n\\n        result[y * bits + x] = total;\\n      }\\n    }\\n\\n    translateBlocksToBits(result, blocksizeX * blocksizeY);\\n    return bitsToHexhash(result);\\n  }\\n\\n  function bmvbhash(data, bits)\\n  {\\n    let x; let y;\\n    let blockWidth; let blockHeight;\\n    let weightTop; let weightBottom; let weightLeft; let weightRight;\\n    let blockTop; let blockBottom; let blockLeft; let blockRight;\\n    let yMult; let yFrac; let yInt;\\n    let xMult; let xFrac; let xInt;\\n    let {width, height, data: imgData} = data;\\n\\n    let evenX = width % bits === 0;\\n    let evenY = height % bits === 0;\\n\\n    if (evenX && evenY)\\n      return bmvbhashEven(data, bits);\\n\\n    // initialize blocks array with 0s\\n    let result = new Array(bits * bits).fill(0);\\n\\n    blockWidth = width / bits;\\n    blockHeight = height / bits;\\n\\n    yInt = 1;\\n    yFrac = 0;\\n\\n    yMult = blockHeight;\\n\\n    weightTop = 1;\\n    weightBottom = 0;\\n\\n    let ii = 0;\\n    for (y = 0; y < height; y++)\\n    {\\n      if (evenY)\\n      {\\n        blockTop = blockBottom = (y / blockHeight) | 0;\\n      }\\n      else\\n      {\\n        if (y + 1 >= yMult)\\n        {\\n          let mod = (y + 1) % blockHeight;\\n          yInt = mod | 0;\\n          yFrac = mod - yInt;\\n\\n          if (blockHeight > 1)\\n            yMult = Math.ceil((y + 1) / blockHeight) * blockHeight;\\n\\n          weightTop = (1 - yFrac);\\n          weightBottom = (yFrac);\\n        }\\n\\n        // yInt will be 0 on bottom/right borders and on block boundaries\\n        if (yInt > 0 || (y + 1) === height)\\n        {\\n          blockTop = blockBottom = (y / blockHeight) | 0;\\n        }\\n        else\\n        {\\n          let div = y / blockHeight;\\n          blockTop = div | 0;\\n          blockBottom = blockTop === div ? blockTop : blockTop + 1;\\n        }\\n      }\\n\\n      xInt = 1;\\n      xFrac = 0;\\n\\n      xMult = blockWidth;\\n\\n      weightLeft = 1;\\n      weightRight = 0;\\n\\n      for (x = 0; x < width; x++)\\n      {\\n        let avgvalue = 765;\\n        let alpha = imgData[ii + 3];\\n        if (alpha !== 0)\\n          avgvalue = imgData[ii] + imgData[ii + 1] + imgData[ii + 2];\\n\\n        if (evenX)\\n        {\\n          blockLeft = blockRight = (x / blockWidth) | 0;\\n        }\\n        else\\n        {\\n          if (x + 1 >= xMult)\\n          {\\n            let mod = (x + 1) % blockWidth;\\n            xInt = mod | 0;\\n            xFrac = mod - xInt;\\n\\n            if (blockWidth > 1)\\n              xMult = Math.ceil((x + 1) / blockWidth) * blockWidth;\\n\\n            weightLeft = 1 - xFrac;\\n            weightRight = xFrac;\\n          }\\n\\n          // xInt will be 0 on bottom/right borders and on block boundaries\\n          if (xInt > 0 || (x + 1) === width)\\n          {\\n            blockLeft = blockRight = (x / blockWidth) | 0;\\n          }\\n          else\\n          {\\n            let div = x / blockWidth;\\n            blockLeft = div | 0;\\n            blockRight = blockLeft === div ? blockLeft : blockLeft + 1;\\n          }\\n        }\\n\\n        // add weighted pixel value to relevant blocks\\n        result[blockTop * bits + blockLeft] +=\\n          avgvalue * weightTop * weightLeft;\\n        result[blockTop * bits + blockRight] +=\\n          avgvalue * weightTop * weightRight;\\n        result[blockBottom * bits + blockLeft] +=\\n          avgvalue * weightBottom * weightLeft;\\n        result[blockBottom * bits + blockRight] +=\\n          avgvalue * weightBottom * weightRight;\\n\\n        ii += 4;\\n\\n        xInt++;\\n      }\\n\\n      yInt++;\\n    }\\n\\n    translateBlocksToBits(result, blockWidth * blockHeight);\\n    return bitsToHexhash(result);\\n  }\\n\\n  return bmvbhash(imageData, blockBits);\\n}\\n\\n/**\\n * Calculate the hamming distance for two hashes in hex format\\n *\\n * The following lines are based off the blockhash-js library which is\\n * licensed under the MIT licence\\n * {@link https://github.com/commonsmachinery/blockhash-js/tree/2084417e40005e37f4ad957dbd2bca08ddc222bc Blockhash.js}\\n *\\n * @param {string} hash1 the first hash of the comparison\\n * @param {string} hash2 the second hash of the comparison\\n * @returns {number} The resulting hamming distance between hash1 and hash2\\n * @private\\n */\\nfunction hammingDistance(hash1, hash2)\\n{\\n  let oneBits = [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4];\\n\\n  let d = 0;\\n  let i;\\n\\n  if (hash1.length !== hash2.length)\\n    throw new Error(\\\"Can't compare hashes with different length\\\");\\n\\n  for (i = 0; i < hash1.length; i++)\\n  {\\n    let n1 = parseInt(hash1[i], 16);\\n    let n2 = parseInt(hash2[i], 16);\\n    d += oneBits[n1 ^ n2];\\n  }\\n  return d;\\n}\\n\\n/**\\n * Hides any HTML element or one of its ancestors matching a CSS selector if\\n * the perceptual hash of the image src or background image of the element\\n * matches the given perceptual hash.\\n * @alias module:content/snippets.hide-if-contains-image-hash\\n *\\n * @param {string} hashes List of comma seperated  perceptual hashes of the\\n *  images that should be blocked, see also `maxDistance`.\\n * @param {?string} [selector] The CSS selector that an HTML element\\n *   containing the given pattern must match. If empty or omitted, defaults\\n *   to the image element itself.\\n * @param {?string} [maxDistance] The maximum hamming distance between `hash`\\n *   and the perceptual hash of the image to be considered a match. Defaults\\n *   to 0.\\n * @param {?number} [blockBits] The block width used to generate the perceptual\\n *   image hash, a number of 4 will split the image into 16 blocks\\n *   (width/4 * height/4). Defaults to 8. The maximum value allowed is 64.\\n * @param {?string} [selection] A string with image coordinates in the format\\n *   XxYxWIDTHxHEIGHT for which a perceptual hash should be computated. If\\n *   ommitted the entire image will be hashed. The X and Y values can be\\n *   negative, in this case they will be relative to the right/bottom corner.\\n *\\n * @since Adblock Plus 3.6.2\\n */\\nfunction hideIfContainsImageHash(hashes,\\n                                 selector,\\n                                 maxDistance,\\n                                 blockBits,\\n                                 selection)\\n{\\n  if (selector == null || selector === \\\"\\\")\\n    selector = \\\"img\\\";\\n\\n  if (maxDistance == null)\\n    maxDistance = 0;\\n\\n  if (blockBits == null)\\n    blockBits = 8;\\n\\n  if (isNaN(maxDistance) || isNaN(blockBits))\\n    return;\\n\\n  blockBits |= 0;\\n\\n  if (blockBits < 1 || blockBits > 64)\\n    return;\\n\\n  selection = (selection || \\\"\\\").split(\\\"x\\\");\\n\\n  let seenImages = new Set();\\n\\n  let callback = images =>\\n  {\\n    for (let image of images)\\n    {\\n      seenImages.add(image.src);\\n\\n      let imageElement = new Image();\\n      imageElement.crossOrigin = \\\"anonymous\\\";\\n      imageElement.onload = () =>\\n      {\\n        let canvas = document.createElement(\\\"canvas\\\");\\n        let context = canvas.getContext(\\\"2d\\\");\\n\\n        let {width, height} = imageElement;\\n\\n        // If a selection is present we are only going to look at that\\n        // part of the image\\n        let sX = parseInt(selection[0], 10) || 0;\\n        let sY = parseInt(selection[1], 10) || 0;\\n        let sWidth = parseInt(selection[2], 10) || width;\\n        let sHeight = parseInt(selection[3], 10) || height;\\n\\n        if (sWidth == 0 || sHeight == 0)\\n          return;\\n\\n        // if sX or sY is negative start from the right/bottom respectively\\n        if (sX < 0)\\n          sX = width + sX;\\n        if (sY < 0)\\n          sY = height + sY;\\n\\n        if (sX < 0)\\n          sX = 0;\\n        if (sY < 0)\\n          sY = 0;\\n        if (sWidth > width)\\n          sWidth = width;\\n        if (sHeight > height)\\n          sHeight = height;\\n\\n        canvas.width = sWidth;\\n        canvas.height = sHeight;\\n\\n        context.drawImage(\\n          imageElement, sX, sY, sWidth, sHeight, 0, 0, sWidth, sHeight);\\n\\n        let imageData = context.getImageData(0, 0, sWidth, sHeight);\\n        let result = hashImage(imageData, blockBits);\\n\\n        for (let hash of hashes.split(\\\",\\\"))\\n        {\\n          if (result.length == hash.length)\\n          {\\n            if (hammingDistance(result, hash) <= maxDistance)\\n            {\\n              let closest = image.closest(selector);\\n              if (closest)\\n              {\\n                hideElement(closest);\\n                return;\\n              }\\n            }\\n          }\\n        }\\n      };\\n      imageElement.src = image.src;\\n    }\\n  };\\n  callback(document.images);\\n\\n  new MutationObserver(() =>\\n  {\\n    let images = new Set();\\n    for (let img of document.images)\\n    {\\n      if (!seenImages.has(img.src))\\n        images.add(img);\\n    }\\n\\n    if (images.size)\\n      callback(images);\\n  }).observe(document, {childList: true, subtree: true, attributes: true});\\n}\\n\\nexports[\\\"hide-if-contains-image-hash\\\"] = hideIfContainsImageHash;\\n\\n/**\\n * Hides any HTML element that uses an `aria-labelledby`, or one of its\\n * ancestors, if the related aria element contains the searched text.\\n * @alias module:content/snippets.hide-if-labelled-by\\n *\\n * @param {string} search The string to look for in HTML elements. If the\\n *   string begins and ends with a slash (`/`), the text in between is treated\\n *   as a regular expression.\\n * @param {string} selector The CSS selector of an HTML element that uses as\\n *   `aria-labelledby` attribute.\\n * @param {?string} [searchSelector] The CSS selector of an ancestor of the\\n *   HTML element that uses as `aria-labelledby` attribute. Defaults to the\\n *   value of the `selector` argument.\\n *\\n * @since Adblock Plus 3.9\\n */\\nfunction hideIfLabelledBy(search, selector, searchSelector = null)\\n{\\n  let sameSelector = searchSelector == null;\\n\\n  let searchRegExp = toRegExp(search);\\n\\n  let matched = new WeakSet();\\n\\n  let callback = () =>\\n  {\\n    for (let node of document.querySelectorAll(selector))\\n    {\\n      let closest = sameSelector ? node : node.closest(searchSelector);\\n      if (!closest || !isVisible(node, getComputedStyle(node), closest))\\n        continue;\\n\\n      let attr = node.getAttribute(\\\"aria-labelledby\\\");\\n      let fallback = () =>\\n      {\\n        if (matched.has(closest))\\n          return;\\n\\n        if (searchRegExp.test(node.getAttribute(\\\"aria-label\\\") || \\\"\\\"))\\n        {\\n          matched.add(closest);\\n          hideElement(closest);\\n        }\\n      };\\n\\n      if (attr)\\n      {\\n        for (let label of attr.split(/\\\\s+/))\\n        {\\n          let target = document.getElementById(label);\\n          if (target)\\n          {\\n            if (!matched.has(target) && searchRegExp.test(target.innerText))\\n            {\\n              matched.add(target);\\n              hideElement(closest);\\n            }\\n          }\\n          else\\n          {\\n            fallback();\\n          }\\n        }\\n      }\\n      else\\n      {\\n        fallback();\\n      }\\n    }\\n  };\\n\\n  let options = {characterData: true, childList: true, subtree: true};\\n  new MutationObserver(callback).observe(document, options);\\n  callback();\\n}\\n\\nexports[\\\"hide-if-labelled-by\\\"] = hideIfLabelledBy;\\n\\n/**\\n * Hide a specific element through a XPath 1.0 query string.\\n * See {@tutorial xpath-filters} to know more.\\n * @alias module:content/snippets.hide-if-matches-xpath\\n *\\n * @param {string} query The XPath query that targets the element to hide.\\n *\\n * @since Adblock Plus 3.9.0\\n */\\nfunction hideIfMatchesXPath(query)\\n{\\n  let {mark, end} = profile(\\\"hide-if-matches-xpath\\\");\\n\\n  let evaluator = new XPathEvaluator();\\n  let expression = evaluator.createExpression(query, null);\\n\\n  // do not use ORDERED_NODE_ITERATOR_TYPE or the test env will fail\\n  let flag = XPathResult.ORDERED_NODE_SNAPSHOT_TYPE;\\n\\n  let callback = () =>\\n  {\\n    mark();\\n\\n    let result = expression.evaluate(document, flag, null);\\n\\n    for (let i = 0, {snapshotLength} = result; i < snapshotLength; i++)\\n    {\\n      let element = result.snapshotItem(i);\\n\\n      if (element.nodeType === Node.ELEMENT_NODE)\\n        hideElement(element);\\n      else\\n        element.textContent = \\\"\\\";\\n    }\\n\\n    end();\\n  };\\n\\n  let options = {characterData: true, childList: true, subtree: true};\\n  new MutationObserver(callback).observe(document, options);\\n  callback();\\n}\\n\\nexports[\\\"hide-if-matches-xpath\\\"] = hideIfMatchesXPath;\\n\\n/**\\n * Whether debug mode is enabled.\\n * @type {boolean}\\n * @private\\n */\\nlet debug = false;\\n\\n/**\\n * Enables debug mode.\\n * @alias module:content/snippets.debug\\n *\\n * @example\\n * example.com#$#debug; log 'Hello, world!'\\n *\\n * @since Adblock Plus 3.8\\n */\\nfunction setDebug()\\n{\\n  debug = true;\\n}\\n\\nexports[\\\"debug\\\"] = setDebug;\\n\\n/**\\n * Default profile(\\\"...\\\") returned object when profile mode is disabled.\\n * @type {Profiler}\\n * @private\\n */\\nlet noopProfile = {\\n  mark() {},\\n  end() {},\\n  toString()\\n  {\\n    return \\\"{mark(){},end(){}}\\\";\\n  }\\n};\\n\\n/**\\n * Whether profile mode is inactive.\\n * @type {boolean}\\n * @private\\n */\\nlet inactiveProfile = true;\\n\\n/**\\n * Enables profile mode.\\n * @alias module:content/snippets.profile\\n * @since Adblock Plus 3.9\\n *\\n * @example\\n * example.com#$#profile; log 'Hello, world!'\\n */\\nfunction setProfile()\\n{\\n  inactiveProfile = false;\\n}\\n\\nexports[\\\"profile\\\"] = setProfile;\\n\\n/**\\n * @typedef {object} Profiler\\n * @property {function} mark Add a `performance.mark(uniqueId)` entry.\\n * @property {function} end Measure and clear `uniqueId` related marks. If a\\n * `true` value is passed as argument, clear related interval and process all\\n * collected samples since the creation of the profiler.\\n * @private\\n */\\n\\n/**\\n * Create an object with `mark()` and `end()` methods to either keep marking a\\n * specific profiled name, or ending it.\\n *\\n * @example\\n * let {mark, end} = profile('console.log');\\n * mark();\\n * console.log(1, 2, 3);\\n * end();\\n *\\n * @param {string} id the callback name or unique ID to profile.\\n * @param {number} [rate] The number of times per minute to process samples.\\n * @returns {Profiler} The profiler with `mark()` and `end(clear = false)`\\n * methods.\\n * @private\\n */\\nfunction profile(id, rate = 10)\\n{\\n  if (inactiveProfile)\\n    return noopProfile;\\n\\n  function sendMessage(message)\\n  {\\n    let {runtime} = (typeof browser == \\\"undefined\\\" ? chrome : browser);\\n    runtime.sendMessage(message);\\n  }\\n\\n  function processSamples()\\n  {\\n    let samples = [];\\n\\n    for (let {name, duration} of performance.getEntriesByType(\\\"measure\\\"))\\n      samples.push({name, duration});\\n\\n    if (samples.length)\\n    {\\n      performance.clearMeasures();\\n\\n      sendMessage({\\n        type: \\\"profiler.sample\\\",\\n        category: \\\"snippets\\\",\\n        samples\\n      });\\n    }\\n  }\\n\\n  // avoid creation of N intervals when the same id is used\\n  // over and over (i.e. within loops or multiple profile calls)\\n  if (!profile[id])\\n  {\\n    profile[id] = setInterval(processSamples,\\n                              Math.round(60000 / Math.min(60, rate)));\\n  }\\n\\n  return {\\n    mark()\\n    {\\n      performance.mark(id);\\n    },\\n    end(clear = false)\\n    {\\n      performance.measure(id, id);\\n      performance.clearMarks(id);\\n      if (clear)\\n      {\\n        clearInterval(profile[id]);\\n        delete profile[id];\\n        processSamples();\\n      }\\n    }\\n  };\\n}\\n\"];\r\n\r\n      let scripts = {{1}};\r\n\r\n      let imports = Object.create(null);\r\n      for (let library of libraries)\r\n      {\r\n        let loadLibrary = new Function(\"exports\", \"environment\", library);\r\n        loadLibrary(imports, {});\r\n      }\r\n\r\n      let {hasOwnProperty} = Object.prototype;\r\n\r\n      if (hasOwnProperty.call(imports, \"prepareInjection\"))\r\n        imports.prepareInjection();\r\n\r\n      for (let script of scripts)\r\n      {\r\n        for (let [name, ...args] of script)\r\n        {\r\n          if (hasOwnProperty.call(imports, name))\r\n          {\r\n            let value = imports[name];\r\n            if (typeof value == \"function\")\r\n              value(...args);\r\n          }\r\n        }\r\n      }\r\n\r\n      if (hasOwnProperty.call(imports, \"commitInjection\"))\r\n        imports.commitInjection();\r\n    }\r\n  "}