| LEFT | RIGHT | 
|    1 /* |    1 /* | 
|    2  * This file is part of Adblock Plus <https://adblockplus.org/>, |    2  * This file is part of Adblock Plus <https://adblockplus.org/>, | 
|    3  * Copyright (C) 2006-present eyeo GmbH |    3  * Copyright (C) 2006-present eyeo GmbH | 
|    4  * |    4  * | 
|    5  * Adblock Plus is free software: you can redistribute it and/or modify |    5  * Adblock Plus is free software: you can redistribute it and/or modify | 
|    6  * it under the terms of the GNU General Public License version 3 as |    6  * it under the terms of the GNU General Public License version 3 as | 
|    7  * published by the Free Software Foundation. |    7  * published by the Free Software Foundation. | 
|    8  * |    8  * | 
|    9  * Adblock Plus is distributed in the hope that it will be useful, |    9  * Adblock Plus is distributed in the hope that it will be useful, | 
|   10  * but WITHOUT ANY WARRANTY; without even the implied warranty of |   10  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|   11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |   11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|   12  * GNU General Public License for more details. |   12  * GNU General Public License for more details. | 
|   13  * |   13  * | 
|   14  * You should have received a copy of the GNU General Public License |   14  * You should have received a copy of the GNU General Public License | 
|   15  * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>. |   15  * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>. | 
|   16  */ |   16  */ | 
|   17  |   17  | 
|   18 "use strict"; |   18 "use strict"; | 
|   19  |   19  | 
|   20 const {textToRegExp, filterToRegExp, splitSelector} = require("../common"); |   20 const {textToRegExp, filterToRegExp, splitSelector} = require("../common"); | 
|   21 const {indexOf} = require("../coreUtils"); |   21 const {indexOf} = require("../coreUtils"); | 
|   22  |   22  | 
|   23 let MIN_INVOCATION_INTERVAL = 3000; |   23 let MIN_INVOCATION_INTERVAL = 3000; | 
|   24 const MAX_SYNCHRONOUS_PROCESSING_TIME = 50; |   24 const MAX_SYNCHRONOUS_PROCESSING_TIME = 50; | 
|   25 const abpSelectorRegexp = /:-abp-([\w-]+)\(/i; |   25 const abpSelectorRegexp = /:-abp-([\w-]+)\(/i; | 
|   26  |   26  | 
 |   27 function getCachedPropertyValue(object, name, defaultValueFunc = () => {}) | 
 |   28 { | 
 |   29   let value = object[name]; | 
 |   30   if (typeof value == "undefined") | 
 |   31     Object.defineProperty(object, name, {value: value = defaultValueFunc()}); | 
 |   32   return value; | 
 |   33 } | 
 |   34  | 
|   27 /** Return position of node from parent. |   35 /** Return position of node from parent. | 
|   28  * @param {Node} node the node to find the position of. |   36  * @param {Node} node the node to find the position of. | 
|   29  * @return {number} One-based index like for :nth-child(), or 0 on error. |   37  * @return {number} One-based index like for :nth-child(), or 0 on error. | 
|   30  */ |   38  */ | 
|   31 function positionInParent(node) |   39 function positionInParent(node) | 
|   32 { |   40 { | 
|   33   return indexOf(node.parentNode.children, node) + 1; |   41   return indexOf(node.parentNode.children, node) + 1; | 
|   34 } |   42 } | 
|   35  |   43  | 
|   36 function makeSelector(node, selector) |   44 function makeSelector(node, selector) | 
| (...skipping 197 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  234  |  242  | 
|  235 const incompletePrefixRegexp = /[\s>+~]$/; |  243 const incompletePrefixRegexp = /[\s>+~]$/; | 
|  236  |  244  | 
|  237 function HasSelector(selectors) |  245 function HasSelector(selectors) | 
|  238 { |  246 { | 
|  239   this._innerSelectors = selectors; |  247   this._innerSelectors = selectors; | 
|  240 } |  248 } | 
|  241  |  249  | 
|  242 HasSelector.prototype = { |  250 HasSelector.prototype = { | 
|  243   requiresHiding: true, |  251   requiresHiding: true, | 
 |  252   dependsOnDOM: true, | 
|  244  |  253  | 
|  245   get dependsOnStyles() |  254   get dependsOnStyles() | 
|  246   { |  255   { | 
|  247     return this._innerSelectors.some(selector => selector.dependsOnStyles); |  256     return this._innerSelectors.some(selector => selector.dependsOnStyles); | 
|  248   }, |  257   }, | 
|  249  |  258  | 
|  250   get dependsOnCharacterData() |  259   get dependsOnCharacterData() | 
|  251   { |  260   { | 
|  252     return this._innerSelectors.some( |  261     return this._innerSelectors.some( | 
|  253       selector => selector.dependsOnCharacterData |  262       selector => selector.dependsOnCharacterData | 
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  296   } |  305   } | 
|  297 }; |  306 }; | 
|  298  |  307  | 
|  299 function ContainsSelector(textContent) |  308 function ContainsSelector(textContent) | 
|  300 { |  309 { | 
|  301   this._regexp = makeRegExpParameter(textContent); |  310   this._regexp = makeRegExpParameter(textContent); | 
|  302 } |  311 } | 
|  303  |  312  | 
|  304 ContainsSelector.prototype = { |  313 ContainsSelector.prototype = { | 
|  305   requiresHiding: true, |  314   requiresHiding: true, | 
 |  315   dependsOnDOM: true, | 
|  306   dependsOnCharacterData: true, |  316   dependsOnCharacterData: true, | 
|  307  |  317  | 
|  308   *getSelectors(prefix, subtree, styles) |  318   *getSelectors(prefix, subtree, styles) | 
|  309   { |  319   { | 
|  310     for (let element of this.getElements(prefix, subtree, styles)) |  320     for (let element of this.getElements(prefix, subtree, styles)) | 
|  311       yield [makeSelector(element, ""), subtree]; |  321       yield [makeSelector(element, ""), subtree]; | 
|  312   }, |  322   }, | 
|  313  |  323  | 
|  314   *getElements(prefix, subtree, styles) |  324   *getElements(prefix, subtree, styles) | 
|  315   { |  325   { | 
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  367         } |  377         } | 
|  368   }, |  378   }, | 
|  369  |  379  | 
|  370   *getSelectors(prefix, subtree, styles) |  380   *getSelectors(prefix, subtree, styles) | 
|  371   { |  381   { | 
|  372     for (let selector of this.findPropsSelectors(styles, prefix, this._regexp)) |  382     for (let selector of this.findPropsSelectors(styles, prefix, this._regexp)) | 
|  373       yield [selector, subtree]; |  383       yield [selector, subtree]; | 
|  374   } |  384   } | 
|  375 }; |  385 }; | 
|  376  |  386  | 
|  377 function isSelectorHidingOnlyPattern(pattern) |  387 function Pattern(selectors, text) | 
|  378 { |  388 { | 
|  379   return pattern.selectors.some(s => s.preferHideWithSelector) && |  389   this.selectors = selectors; | 
|  380     !pattern.selectors.some(s => s.requiresHiding); |  390   this.text = text; | 
 |  391 } | 
 |  392  | 
 |  393 Pattern.prototype = { | 
 |  394   isSelectorHidingOnlyPattern() | 
 |  395   { | 
 |  396     return getCachedPropertyValue( | 
 |  397       this, "_selectorHidingOnlyPattern", | 
 |  398       () => this.selectors.some(selector => selector.preferHideWithSelector) && | 
 |  399             !this.selectors.some(selector => selector.requiresHiding) | 
 |  400     ); | 
 |  401   }, | 
 |  402  | 
 |  403   get dependsOnStyles() | 
 |  404   { | 
 |  405     return getCachedPropertyValue( | 
 |  406       this, "_dependsOnStyles", | 
 |  407       () => this.selectors.some(selector => selector.dependsOnStyles) | 
 |  408     ); | 
 |  409   }, | 
 |  410  | 
 |  411   get dependsOnDOM() | 
 |  412   { | 
 |  413     return getCachedPropertyValue( | 
 |  414       this, "_dependsOnDOM", | 
 |  415       () => this.selectors.some(selector => selector.dependsOnDOM) | 
 |  416     ); | 
 |  417   }, | 
 |  418  | 
 |  419   get dependsOnStylesAndDOM() | 
 |  420   { | 
 |  421     return getCachedPropertyValue( | 
 |  422       this, "_dependsOnStylesAndDOM", | 
 |  423       () => this.selectors.some(selector => selector.dependsOnStyles && | 
 |  424                                             selector.dependsOnDOM) | 
 |  425     ); | 
 |  426   }, | 
 |  427  | 
 |  428   get maybeDependsOnAttributes() | 
 |  429   { | 
 |  430     // Observe changes to attributes if either there's a plain selector that | 
 |  431     // looks like an ID selector, class selector, or attribute selector in one | 
 |  432     // of the patterns (e.g. "a[href='https://example.com/']") | 
 |  433     // or there's a properties selector nested inside a has selector | 
 |  434     // (e.g. "div:-abp-has(:-abp-properties(color: blue))") | 
 |  435     return getCachedPropertyValue( | 
 |  436       this, "_maybeDependsOnAttributes", | 
 |  437       () => this.selectors.some( | 
 |  438               selector => selector.maybeDependsOnAttributes || | 
 |  439                           (selector instanceof HasSelector && | 
 |  440                            selector.dependsOnStyles) | 
 |  441             ) | 
 |  442     ); | 
 |  443   }, | 
 |  444  | 
 |  445   get dependsOnCharacterData() | 
 |  446   { | 
 |  447     // Observe changes to character data only if there's a contains selector in | 
 |  448     // one of the patterns. | 
 |  449     return getCachedPropertyValue( | 
 |  450       this, "_dependsOnCharacterData", | 
 |  451       () => this.selectors.some(selector => selector.dependsOnCharacterData) | 
 |  452     ); | 
 |  453   } | 
 |  454 }; | 
 |  455  | 
 |  456 function filterPatterns(patterns, {stylesheets, mutations}) | 
 |  457 { | 
 |  458   if (!stylesheets && !mutations) | 
 |  459     return patterns.slice(); | 
 |  460  | 
 |  461   return patterns.filter( | 
 |  462     pattern => (stylesheets && pattern.dependsOnStyles) || | 
 |  463                (mutations && pattern.dependsOnDOM) | 
 |  464   ); | 
|  381 } |  465 } | 
|  382  |  466  | 
|  383 function shouldObserveAttributes(patterns) |  467 function shouldObserveAttributes(patterns) | 
|  384 { |  468 { | 
|  385   // Observe changes to attributes if either there's a plain selector that |  469   return patterns.some(pattern => pattern.maybeDependsOnAttributes); | 
|  386   // looks like an ID selector, class selector, or attribute selector in one of |  | 
|  387   // the patterns (e.g. "a[href='https://example.com/']") |  | 
|  388   // or there's a properties selector nested inside a has selector |  | 
|  389   // (e.g. "div:-abp-has(:-abp-properties(color: blue))") |  | 
|  390   return patterns.some( |  | 
|  391     pattern => pattern.selectors.some( |  | 
|  392       selector => selector.maybeDependsOnAttributes || |  | 
|  393                   (selector instanceof HasSelector && |  | 
|  394                    selector.dependsOnStyles) |  | 
|  395     ) |  | 
|  396   ); |  | 
|  397 } |  470 } | 
|  398  |  471  | 
|  399 function shouldObserveCharacterData(patterns) |  472 function shouldObserveCharacterData(patterns) | 
|  400 { |  473 { | 
|  401   // Observe changes to character data only if there's a contains selector in |  474   return patterns.some(pattern => pattern.dependsOnCharacterData); | 
|  402   // one of the patterns. |  | 
|  403   return patterns.some( |  | 
|  404     pattern => pattern.selectors.some( |  | 
|  405       selector => selector.dependsOnCharacterData |  | 
|  406     ) |  | 
|  407   ); |  | 
|  408 } |  475 } | 
|  409  |  476  | 
|  410 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc) |  477 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc) | 
|  411 { |  478 { | 
|  412   this.document = document; |  479   this.document = document; | 
|  413   this.addSelectorsFunc = addSelectorsFunc; |  480   this.addSelectorsFunc = addSelectorsFunc; | 
|  414   this.hideElemsFunc = hideElemsFunc; |  481   this.hideElemsFunc = hideElemsFunc; | 
|  415   this.observer = new MutationObserver(this.observe.bind(this)); |  482   this.observer = new MutationObserver(this.observe.bind(this)); | 
|  416 } |  483 } | 
|  417  |  484  | 
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  492     return selectors; |  559     return selectors; | 
|  493   }, |  560   }, | 
|  494  |  561  | 
|  495   /** |  562   /** | 
|  496    * Processes the current document and applies all rules to it. |  563    * Processes the current document and applies all rules to it. | 
|  497    * @param {CSSStyleSheet[]} [stylesheets] |  564    * @param {CSSStyleSheet[]} [stylesheets] | 
|  498    *    The list of new stylesheets that have been added to the document and |  565    *    The list of new stylesheets that have been added to the document and | 
|  499    *    made reprocessing necessary. This parameter shouldn't be passed in for |  566    *    made reprocessing necessary. This parameter shouldn't be passed in for | 
|  500    *    the initial processing, all of document's stylesheets will be considered |  567    *    the initial processing, all of document's stylesheets will be considered | 
|  501    *    then and all rules, including the ones not dependent on styles. |  568    *    then and all rules, including the ones not dependent on styles. | 
 |  569    * @param {MutationRecord[]} [mutations] | 
 |  570    *    The list of DOM mutations that have been applied to the document and | 
 |  571    *    made reprocessing necessary. This parameter shouldn't be passed in for | 
 |  572    *    the initial processing, the entire document will be considered | 
 |  573    *    then and all rules, including the ones not dependent on the DOM. | 
|  502    * @param {function} [done] |  574    * @param {function} [done] | 
|  503    *    Callback to call when done. |  575    *    Callback to call when done. | 
|  504    */ |  576    */ | 
|  505   _addSelectors(stylesheets, done) |  577   _addSelectors(stylesheets, mutations, done) | 
|  506   { |  578   { | 
 |  579     let patterns = filterPatterns(this.patterns, {stylesheets, mutations}); | 
 |  580  | 
|  507     let selectors = []; |  581     let selectors = []; | 
|  508     let selectorFilters = []; |  582     let selectorFilters = []; | 
|  509  |  583  | 
|  510     let elements = []; |  584     let elements = []; | 
|  511     let elementFilters = []; |  585     let elementFilters = []; | 
|  512  |  586  | 
|  513     let cssStyles = []; |  587     let cssStyles = []; | 
|  514  |  588  | 
|  515     let stylesheetOnlyChange = !!stylesheets; |  589     // If neither any style sheets nor any DOM mutations have been specified, | 
|  516     if (!stylesheets) |  590     // do full processing. | 
 |  591     if (!stylesheets && !mutations) | 
|  517       stylesheets = this.document.styleSheets; |  592       stylesheets = this.document.styleSheets; | 
|  518  |  593  | 
|  519     for (let stylesheet of stylesheets) |  594     // If there are any DOM mutations and any of the patterns depends on both | 
 |  595     // style sheets and the DOM (e.g. -abp-has(-abp-properties)), find all the | 
 |  596     // rules in every style sheet in the document, because we need to run | 
 |  597     // querySelectorAll afterwards. On the other hand, if we only have patterns | 
 |  598     // that depend on either styles or DOM both not both | 
 |  599     // (e.g. -abp-properties or -abp-contains), we can skip this part. | 
 |  600     if (mutations && patterns.some(pattern => pattern.dependsOnStylesAndDOM)) | 
 |  601       stylesheets = this.document.styleSheets; | 
 |  602  | 
 |  603     for (let stylesheet of stylesheets || []) | 
|  520     { |  604     { | 
|  521       // Explicitly ignore third-party stylesheets to ensure consistent behavior |  605       // Explicitly ignore third-party stylesheets to ensure consistent behavior | 
|  522       // between Firefox and Chrome. |  606       // between Firefox and Chrome. | 
|  523       if (!this.isSameOrigin(stylesheet)) |  607       if (!this.isSameOrigin(stylesheet)) | 
|  524         continue; |  608         continue; | 
|  525  |  609  | 
|  526       let rules = stylesheet.cssRules; |  610       let rules = stylesheet.cssRules; | 
|  527       if (!rules) |  611       if (!rules) | 
|  528         continue; |  612         continue; | 
|  529  |  613  | 
|  530       for (let rule of rules) |  614       for (let rule of rules) | 
|  531       { |  615       { | 
|  532         if (rule.type != rule.STYLE_RULE) |  616         if (rule.type != rule.STYLE_RULE) | 
|  533           continue; |  617           continue; | 
|  534  |  618  | 
|  535         cssStyles.push(stringifyStyle(rule)); |  619         cssStyles.push(stringifyStyle(rule)); | 
|  536       } |  620       } | 
|  537     } |  621     } | 
|  538  |  622  | 
|  539     let patterns = this.patterns.slice(); |  | 
|  540     let pattern = null; |  623     let pattern = null; | 
|  541     let generator = null; |  624     let generator = null; | 
|  542  |  625  | 
|  543     let processPatterns = () => |  626     let processPatterns = () => | 
|  544     { |  627     { | 
|  545       let cycleStart = performance.now(); |  628       let cycleStart = performance.now(); | 
|  546  |  629  | 
|  547       if (!pattern) |  630       if (!pattern) | 
|  548       { |  631       { | 
|  549         if (!patterns.length) |  632         if (!patterns.length) | 
|  550         { |  633         { | 
|  551           this.addSelectorsFunc(selectors, selectorFilters); |  634           if (selectors.length > 0) | 
|  552           this.hideElemsFunc(elements, elementFilters); |  635             this.addSelectorsFunc(selectors, selectorFilters); | 
 |  636           if (elements.length > 0) | 
 |  637             this.hideElemsFunc(elements, elementFilters); | 
|  553           if (typeof done == "function") |  638           if (typeof done == "function") | 
|  554             done(); |  639             done(); | 
|  555           return; |  640           return; | 
|  556         } |  641         } | 
|  557  |  642  | 
|  558         pattern = patterns.shift(); |  643         pattern = patterns.shift(); | 
|  559  |  644  | 
|  560         if (stylesheetOnlyChange && |  | 
|  561             !pattern.selectors.some(selector => selector.dependsOnStyles)) |  | 
|  562         { |  | 
|  563           pattern = null; |  | 
|  564           return processPatterns(); |  | 
|  565         } |  | 
|  566         generator = evaluate(pattern.selectors, 0, "", |  645         generator = evaluate(pattern.selectors, 0, "", | 
|  567                              this.document, cssStyles); |  646                              this.document, cssStyles); | 
|  568       } |  647       } | 
|  569       for (let selector of generator) |  648       for (let selector of generator) | 
|  570       { |  649       { | 
|  571         if (selector != null) |  650         if (selector != null) | 
|  572         { |  651         { | 
|  573           if (isSelectorHidingOnlyPattern(pattern)) |  652           if (pattern.isSelectorHidingOnlyPattern()) | 
|  574           { |  653           { | 
|  575             selectors.push(selector); |  654             selectors.push(selector); | 
|  576             selectorFilters.push(pattern.text); |  655             selectorFilters.push(pattern.text); | 
|  577           } |  656           } | 
|  578           else |  657           else | 
|  579           { |  658           { | 
|  580             for (let element of this.document.querySelectorAll(selector)) |  659             for (let element of this.document.querySelectorAll(selector)) | 
|  581             { |  660             { | 
|  582               elements.push(element); |  661               elements.push(element); | 
|  583               elementFilters.push(pattern.text); |  662               elementFilters.push(pattern.text); | 
| (...skipping 26 matching lines...) Expand all  Loading... | 
|  610   }, |  689   }, | 
|  611  |  690  | 
|  612   _filteringInProgress: false, |  691   _filteringInProgress: false, | 
|  613   _lastInvocation: -MIN_INVOCATION_INTERVAL, |  692   _lastInvocation: -MIN_INVOCATION_INTERVAL, | 
|  614   _scheduledProcessing: null, |  693   _scheduledProcessing: null, | 
|  615  |  694  | 
|  616   /** |  695   /** | 
|  617    * Re-run filtering either immediately or queued. |  696    * Re-run filtering either immediately or queued. | 
|  618    * @param {CSSStyleSheet[]} [stylesheets] |  697    * @param {CSSStyleSheet[]} [stylesheets] | 
|  619    *    new stylesheets to be processed. This parameter should be omitted |  698    *    new stylesheets to be processed. This parameter should be omitted | 
|  620    *    for DOM modification (full reprocessing required). |  699    *    for full reprocessing. | 
 |  700    * @param {MutationRecord[]} [mutations] | 
 |  701    *    new DOM mutations to be processed. This parameter should be omitted | 
 |  702    *    for full reprocessing. | 
|  621    */ |  703    */ | 
|  622   queueFiltering(stylesheets) |  704   queueFiltering(stylesheets, mutations) | 
|  623   { |  705   { | 
|  624     let completion = () => |  706     let completion = () => | 
|  625     { |  707     { | 
|  626       this._lastInvocation = performance.now(); |  708       this._lastInvocation = performance.now(); | 
|  627       this._filteringInProgress = false; |  709       this._filteringInProgress = false; | 
|  628       if (this._scheduledProcessing) |  710       if (this._scheduledProcessing) | 
|  629       { |  711       { | 
|  630         let newStylesheets = this._scheduledProcessing.stylesheets; |  712         let params = Object.assign({}, this._scheduledProcessing); | 
|  631         this._scheduledProcessing = null; |  713         this._scheduledProcessing = null; | 
|  632         this.queueFiltering(newStylesheets); |  714         this.queueFiltering(params.stylesheets, params.mutations); | 
|  633       } |  715       } | 
|  634     }; |  716     }; | 
|  635  |  717  | 
|  636     if (this._scheduledProcessing) |  718     if (this._scheduledProcessing) | 
|  637     { |  719     { | 
|  638       if (!stylesheets) |  720       if (!stylesheets && !mutations) | 
|  639         this._scheduledProcessing.stylesheets = null; |  721       { | 
|  640       else if (this._scheduledProcessing.stylesheets) |  722         this._scheduledProcessing = {}; | 
|  641         this._scheduledProcessing.stylesheets.push(...stylesheets); |  723       } | 
 |  724       else | 
 |  725       { | 
 |  726         if (stylesheets) | 
 |  727         { | 
 |  728           if (!this._scheduledProcessing.stylesheets) | 
 |  729             this._scheduledProcessing.stylesheets = []; | 
 |  730           this._scheduledProcessing.stylesheets.push(...stylesheets); | 
 |  731         } | 
 |  732         if (mutations) | 
 |  733         { | 
 |  734           if (!this._scheduledProcessing.mutations) | 
 |  735             this._scheduledProcessing.mutations = []; | 
 |  736           this._scheduledProcessing.mutations.push(...mutations); | 
 |  737         } | 
 |  738       } | 
|  642     } |  739     } | 
|  643     else if (this._filteringInProgress) |  740     else if (this._filteringInProgress) | 
|  644     { |  741     { | 
|  645       this._scheduledProcessing = {stylesheets}; |  742       this._scheduledProcessing = {stylesheets, mutations}; | 
|  646     } |  743     } | 
|  647     else if (performance.now() - this._lastInvocation < MIN_INVOCATION_INTERVAL) |  744     else if (performance.now() - this._lastInvocation < MIN_INVOCATION_INTERVAL) | 
|  648     { |  745     { | 
|  649       this._scheduledProcessing = {stylesheets}; |  746       this._scheduledProcessing = {stylesheets, mutations}; | 
|  650       setTimeout(() => |  747       setTimeout(() => | 
|  651       { |  748       { | 
|  652         let newStylesheets = this._scheduledProcessing.stylesheets; |  749         let params = Object.assign({}, this._scheduledProcessing); | 
|  653         this._filteringInProgress = true; |  750         this._filteringInProgress = true; | 
|  654         this._scheduledProcessing = null; |  751         this._scheduledProcessing = null; | 
|  655         this._addSelectors(newStylesheets, completion); |  752         this._addSelectors(params.stylesheets, params.mutations, completion); | 
|  656       }, |  753       }, | 
|  657       MIN_INVOCATION_INTERVAL - (performance.now() - this._lastInvocation)); |  754       MIN_INVOCATION_INTERVAL - (performance.now() - this._lastInvocation)); | 
|  658     } |  755     } | 
|  659     else if (this.document.readyState == "loading") |  756     else if (this.document.readyState == "loading") | 
|  660     { |  757     { | 
|  661       this._scheduledProcessing = {stylesheets}; |  758       this._scheduledProcessing = {stylesheets, mutations}; | 
|  662       let handler = () => |  759       let handler = () => | 
|  663       { |  760       { | 
|  664         document.removeEventListener("DOMContentLoaded", handler); |  761         document.removeEventListener("DOMContentLoaded", handler); | 
|  665         let newStylesheets = this._scheduledProcessing.stylesheets; |  762         let params = Object.assign({}, this._scheduledProcessing); | 
|  666         this._filteringInProgress = true; |  763         this._filteringInProgress = true; | 
|  667         this._scheduledProcessing = null; |  764         this._scheduledProcessing = null; | 
|  668         this._addSelectors(newStylesheets, completion); |  765         this._addSelectors(params.stylesheets, params.mutations, completion); | 
|  669       }; |  766       }; | 
|  670       document.addEventListener("DOMContentLoaded", handler); |  767       document.addEventListener("DOMContentLoaded", handler); | 
|  671     } |  768     } | 
|  672     else |  769     else | 
|  673     { |  770     { | 
|  674       this._filteringInProgress = true; |  771       this._filteringInProgress = true; | 
|  675       this._addSelectors(stylesheets, completion); |  772       this._addSelectors(stylesheets, mutations, completion); | 
|  676     } |  773     } | 
|  677   }, |  774   }, | 
|  678  |  775  | 
|  679   onLoad(event) |  776   onLoad(event) | 
|  680   { |  777   { | 
|  681     let stylesheet = event.target.sheet; |  778     let stylesheet = event.target.sheet; | 
|  682     if (stylesheet) |  779     if (stylesheet) | 
|  683       this.queueFiltering([stylesheet]); |  780       this.queueFiltering([stylesheet]); | 
|  684   }, |  781   }, | 
|  685  |  782  | 
|  686   observe(mutations) |  783   observe(mutations) | 
|  687   { |  784   { | 
|  688     this.queueFiltering(); |  785     this.queueFiltering(null, mutations); | 
|  689   }, |  786   }, | 
|  690  |  787  | 
|  691   apply(patterns) |  788   apply(patterns) | 
|  692   { |  789   { | 
|  693     this.patterns = []; |  790     this.patterns = []; | 
|  694     for (let pattern of patterns) |  791     for (let pattern of patterns) | 
|  695     { |  792     { | 
|  696       let selectors = this.parseSelector(pattern.selector); |  793       let selectors = this.parseSelector(pattern.selector); | 
|  697       if (selectors != null && selectors.length > 0) |  794       if (selectors != null && selectors.length > 0) | 
|  698         this.patterns.push({selectors, text: pattern.text}); |  795         this.patterns.push(new Pattern(selectors, pattern.text)); | 
|  699     } |  796     } | 
|  700  |  797  | 
|  701     if (this.patterns.length > 0) |  798     if (this.patterns.length > 0) | 
|  702     { |  799     { | 
|  703       this.queueFiltering(); |  800       this.queueFiltering(); | 
|  704       this.observer.observe( |  801       this.observer.observe( | 
|  705         this.document, |  802         this.document, | 
|  706         { |  803         { | 
|  707           childList: true, |  804           childList: true, | 
|  708           attributes: shouldObserveAttributes(this.patterns), |  805           attributes: shouldObserveAttributes(this.patterns), | 
|  709           characterData: shouldObserveCharacterData(this.patterns), |  806           characterData: shouldObserveCharacterData(this.patterns), | 
|  710           subtree: true |  807           subtree: true | 
|  711         } |  808         } | 
|  712       ); |  809       ); | 
|  713       this.document.addEventListener("load", this.onLoad.bind(this), true); |  810       this.document.addEventListener("load", this.onLoad.bind(this), true); | 
|  714     } |  811     } | 
|  715   } |  812   } | 
|  716 }; |  813 }; | 
|  717  |  814  | 
|  718 exports.ElemHideEmulation = ElemHideEmulation; |  815 exports.ElemHideEmulation = ElemHideEmulation; | 
| LEFT | RIGHT |