| OLD | NEW | 
|    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 | 
| (...skipping 18 matching lines...) Expand all  Loading... | 
|   29  */ |   29  */ | 
|   30 function positionInParent(node) |   30 function positionInParent(node) | 
|   31 { |   31 { | 
|   32   let {children} = node.parentNode; |   32   let {children} = node.parentNode; | 
|   33   for (let i = 0; i < children.length; i++) |   33   for (let i = 0; i < children.length; i++) | 
|   34     if (children[i] == node) |   34     if (children[i] == node) | 
|   35       return i + 1; |   35       return i + 1; | 
|   36   return 0; |   36   return 0; | 
|   37 } |   37 } | 
|   38  |   38  | 
 |   39 function isShadowRoot(node) | 
 |   40 { | 
 |   41   return typeof ShadowRoot != "undefined" && node instanceof ShadowRoot; | 
 |   42 } | 
 |   43  | 
|   39 function makeSelector(node, selector) |   44 function makeSelector(node, selector) | 
|   40 { |   45 { | 
|   41   if (node == null) |   46   if (node == null) | 
|   42     return null; |   47     return null; | 
|   43   if (!node.parentElement) |   48  | 
 |   49   // If this is the topmost element in a shadow DOM, climb up one more level | 
 |   50   // and then use a ":host" prefix. | 
 |   51   if (!node.parentElement && !isShadowRoot(node.parentNode)) | 
|   44   { |   52   { | 
|   45     let newSelector = ":root"; |   53     let newSelector = isShadowRoot(node) ? ":host" : ":root"; | 
|   46     if (selector) |   54     if (selector) | 
|   47       newSelector += " > " + selector; |   55       newSelector += " > " + selector; | 
|   48     return newSelector; |   56     return newSelector; | 
|   49   } |   57   } | 
|   50   let idx = positionInParent(node); |   58   let idx = positionInParent(node); | 
|   51   if (idx > 0) |   59   if (idx > 0) | 
|   52   { |   60   { | 
|   53     let newSelector = `${node.tagName}:nth-child(${idx})`; |   61     let newSelector = `${node.tagName}:nth-child(${idx})`; | 
|   54     if (selector) |   62     if (selector) | 
|   55       newSelector += " > " + selector; |   63       newSelector += " > " + selector; | 
|   56     return makeSelector(node.parentElement, newSelector); |   64     return makeSelector(node.parentElement || node.parentNode, newSelector); | 
|   57   } |   65   } | 
|   58  |   66  | 
|   59   return selector; |   67   return selector; | 
|   60 } |   68 } | 
|   61  |   69  | 
|   62 function parseSelectorContent(content, startIndex) |   70 function parseSelectorContent(content, startIndex) | 
|   63 { |   71 { | 
|   64   let parens = 1; |   72   let parens = 1; | 
|   65   let quote = null; |   73   let quote = null; | 
|   66   let i = startIndex; |   74   let i = startIndex; | 
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  136       yield null; |  144       yield null; | 
|  137     else |  145     else | 
|  138       yield* evaluate(chain, index + 1, selector, element, styles); |  146       yield* evaluate(chain, index + 1, selector, element, styles); | 
|  139   } |  147   } | 
|  140   // Just in case the getSelectors() generator above had to run some heavy |  148   // Just in case the getSelectors() generator above had to run some heavy | 
|  141   // document.querySelectorAll() call which didn't produce any results, make |  149   // document.querySelectorAll() call which didn't produce any results, make | 
|  142   // sure there is at least one point where execution can pause. |  150   // sure there is at least one point where execution can pause. | 
|  143   yield null; |  151   yield null; | 
|  144 } |  152 } | 
|  145  |  153  | 
 |  154 function isDescendantOf(node, subtrees) | 
 |  155 { | 
 |  156   return subtrees.some(subtree => subtree.contains(node)); | 
 |  157 } | 
 |  158  | 
 |  159 function* extractAddedSubtrees(mutations) | 
 |  160 { | 
 |  161   let knownSubtrees = []; | 
 |  162  | 
 |  163   for (let mutation of mutations) | 
 |  164   { | 
 |  165     for (let node of mutation.addedNodes) | 
 |  166     { | 
 |  167       if (node instanceof Element && !isDescendantOf(node, knownSubtrees)) | 
 |  168       { | 
 |  169         knownSubtrees.push(node); | 
 |  170         yield node; | 
 |  171       } | 
 |  172     } | 
 |  173   } | 
 |  174 } | 
 |  175  | 
 |  176 function* traverse(nodes) | 
 |  177 { | 
 |  178   for (let node of nodes) | 
 |  179   { | 
 |  180     yield* traverse(node.children); | 
 |  181     yield node; | 
 |  182   } | 
 |  183 } | 
 |  184  | 
 |  185 function niceLoop(iterator, callback) | 
 |  186 { | 
 |  187   let loop = () => | 
 |  188   { | 
 |  189     let cycleStart = performance.now(); | 
 |  190  | 
 |  191     for (let next = iterator.next(); !next.done; next = iterator.next()) | 
 |  192     { | 
 |  193       callback(next.value); | 
 |  194  | 
 |  195       if (performance.now() - cycleStart > MAX_SYNCHRONOUS_PROCESSING_TIME) | 
 |  196       { | 
 |  197         setTimeout(loop, 0); | 
 |  198         return; | 
 |  199       } | 
 |  200     } | 
 |  201   }; | 
 |  202  | 
 |  203   loop(); | 
 |  204 } | 
 |  205  | 
|  146 function PlainSelector(selector) |  206 function PlainSelector(selector) | 
|  147 { |  207 { | 
|  148   this._selector = selector; |  208   this._selector = selector; | 
|  149 } |  209 } | 
|  150  |  210  | 
|  151 PlainSelector.prototype = { |  211 PlainSelector.prototype = { | 
|  152   /** |  212   /** | 
|  153    * Generator function returning a pair of selector |  213    * Generator function returning a pair of selector | 
|  154    * string and subtree. |  214    * string and subtree. | 
|  155    * @param {string} prefix the prefix for the selector. |  215    * @param {string} prefix the prefix for the selector. | 
| (...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  295       yield [selector, subtree]; |  355       yield [selector, subtree]; | 
|  296   } |  356   } | 
|  297 }; |  357 }; | 
|  298  |  358  | 
|  299 function isSelectorHidingOnlyPattern(pattern) |  359 function isSelectorHidingOnlyPattern(pattern) | 
|  300 { |  360 { | 
|  301   return pattern.selectors.some(s => s.preferHideWithSelector) && |  361   return pattern.selectors.some(s => s.preferHideWithSelector) && | 
|  302     !pattern.selectors.some(s => s.requiresHiding); |  362     !pattern.selectors.some(s => s.requiresHiding); | 
|  303 } |  363 } | 
|  304  |  364  | 
|  305 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc) |  365 function ElemHideEmulation(document, root, addSelectorsFunc, hideElemsFunc, | 
 |  366                            shadowAttachedEventType) | 
|  306 { |  367 { | 
|  307   this.document = document; |  368   this.document = document; | 
 |  369   this.root = root || document; | 
|  308   this.addSelectorsFunc = addSelectorsFunc; |  370   this.addSelectorsFunc = addSelectorsFunc; | 
|  309   this.hideElemsFunc = hideElemsFunc; |  371   this.hideElemsFunc = hideElemsFunc; | 
 |  372   this.shadowAttachedEventType = shadowAttachedEventType; | 
 |  373   this.patterns = []; | 
|  310   this.observer = new MutationObserver(this.observe.bind(this)); |  374   this.observer = new MutationObserver(this.observe.bind(this)); | 
 |  375   this.shadowEmulations = new WeakMap(); | 
 |  376  | 
 |  377   if (shadowAttachedEventType) | 
 |  378   { | 
 |  379     this.root.addEventListener(shadowAttachedEventType, | 
 |  380                                this.onShadowAttached.bind(this), true); | 
 |  381   } | 
 |  382  | 
 |  383   if (this.root == this.document) | 
 |  384     this.document.addEventListener("load", this.onLoad.bind(this), true); | 
 |  385   else | 
 |  386     this.findShadowRoots(this.root.children); | 
|  311 } |  387 } | 
|  312  |  388  | 
|  313 ElemHideEmulation.prototype = { |  389 ElemHideEmulation.prototype = { | 
|  314   isSameOrigin(stylesheet) |  390   isSameOrigin(stylesheet) | 
|  315   { |  391   { | 
|  316     try |  392     try | 
|  317     { |  393     { | 
|  318       return new URL(stylesheet.href).origin == this.document.location.origin; |  394       return new URL(stylesheet.href).origin == this.document.location.origin; | 
|  319     } |  395     } | 
|  320     catch (e) |  396     catch (e) | 
| (...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  402     let selectors = []; |  478     let selectors = []; | 
|  403     let selectorFilters = []; |  479     let selectorFilters = []; | 
|  404  |  480  | 
|  405     let elements = []; |  481     let elements = []; | 
|  406     let elementFilters = []; |  482     let elementFilters = []; | 
|  407  |  483  | 
|  408     let cssStyles = []; |  484     let cssStyles = []; | 
|  409  |  485  | 
|  410     let stylesheetOnlyChange = !!stylesheets; |  486     let stylesheetOnlyChange = !!stylesheets; | 
|  411     if (!stylesheets) |  487     if (!stylesheets) | 
|  412       stylesheets = this.document.styleSheets; |  488       stylesheets = this.root.styleSheets; | 
|  413  |  489  | 
|  414     for (let stylesheet of stylesheets) |  490     for (let stylesheet of stylesheets) | 
|  415     { |  491     { | 
|  416       // Explicitly ignore third-party stylesheets to ensure consistent behavior |  492       // Explicitly ignore third-party stylesheets to ensure consistent behavior | 
|  417       // between Firefox and Chrome. |  493       // between Firefox and Chrome. | 
|  418       if (!this.isSameOrigin(stylesheet)) |  494       if (!this.isSameOrigin(stylesheet)) | 
|  419         continue; |  495         continue; | 
|  420  |  496  | 
|  421       let rules = stylesheet.cssRules; |  497       let rules = stylesheet.cssRules; | 
|  422       if (!rules) |  498       if (!rules) | 
| (...skipping 29 matching lines...) Expand all  Loading... | 
|  452  |  528  | 
|  453         pattern = patterns.shift(); |  529         pattern = patterns.shift(); | 
|  454  |  530  | 
|  455         if (stylesheetOnlyChange && |  531         if (stylesheetOnlyChange && | 
|  456             !pattern.selectors.some(selector => selector.dependsOnStyles)) |  532             !pattern.selectors.some(selector => selector.dependsOnStyles)) | 
|  457         { |  533         { | 
|  458           pattern = null; |  534           pattern = null; | 
|  459           return processPatterns(); |  535           return processPatterns(); | 
|  460         } |  536         } | 
|  461         generator = evaluate(pattern.selectors, 0, "", |  537         generator = evaluate(pattern.selectors, 0, "", | 
|  462                              this.document, cssStyles); |  538                              this.root, cssStyles); | 
|  463       } |  539       } | 
|  464       for (let selector of generator) |  540       for (let selector of generator) | 
|  465       { |  541       { | 
|  466         if (selector != null) |  542         if (selector != null) | 
|  467         { |  543         { | 
|  468           if (isSelectorHidingOnlyPattern(pattern)) |  544           if (isSelectorHidingOnlyPattern(pattern)) | 
|  469           { |  545           { | 
|  470             selectors.push(selector); |  546             selectors.push(selector); | 
|  471             selectorFilters.push(pattern.text); |  547             selectorFilters.push(pattern.text); | 
|  472           } |  548           } | 
|  473           else |  549           else | 
|  474           { |  550           { | 
|  475             for (let element of this.document.querySelectorAll(selector)) |  551             for (let element of this.root.querySelectorAll(selector)) | 
|  476             { |  552             { | 
|  477               elements.push(element); |  553               elements.push(element); | 
|  478               elementFilters.push(pattern.text); |  554               elementFilters.push(pattern.text); | 
|  479             } |  555             } | 
|  480           } |  556           } | 
|  481         } |  557         } | 
|  482         if (performance.now() - cycleStart > MAX_SYNCHRONOUS_PROCESSING_TIME) |  558         if (performance.now() - cycleStart > MAX_SYNCHRONOUS_PROCESSING_TIME) | 
|  483         { |  559         { | 
|  484           setTimeout(processPatterns, 0); |  560           setTimeout(processPatterns, 0); | 
|  485           return; |  561           return; | 
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  553     } |  629     } | 
|  554     else |  630     else | 
|  555     { |  631     { | 
|  556       this._filteringInProgress = true; |  632       this._filteringInProgress = true; | 
|  557       this._addSelectors(stylesheets, completion); |  633       this._addSelectors(stylesheets, completion); | 
|  558     } |  634     } | 
|  559   }, |  635   }, | 
|  560  |  636  | 
|  561   onLoad(event) |  637   onLoad(event) | 
|  562   { |  638   { | 
 |  639     this.findShadowRoots(this.root.children); | 
 |  640  | 
 |  641     if (this.patterns.length == 0) | 
 |  642       return; | 
 |  643  | 
|  563     let stylesheet = event.target.sheet; |  644     let stylesheet = event.target.sheet; | 
|  564     if (stylesheet) |  645     if (stylesheet) | 
|  565       this.queueFiltering([stylesheet]); |  646       this.queueFiltering([stylesheet]); | 
|  566   }, |  647   }, | 
|  567  |  648  | 
 |  649   onShadowAttached(event) | 
 |  650   { | 
 |  651     event.stopImmediatePropagation(); | 
 |  652  | 
 |  653     if (this.patterns.length == 0) | 
 |  654       return; | 
 |  655  | 
 |  656     // The shadow root may not be available if it's a closed shadow root. | 
 |  657     let shadowRoot = event.target.shadowRoot; | 
 |  658     if (!shadowRoot) | 
 |  659       return; | 
 |  660  | 
 |  661     this.addShadowRoot(shadowRoot); | 
 |  662   }, | 
 |  663  | 
 |  664   addShadowRoot(shadowRoot) | 
 |  665   { | 
 |  666     if (!this.shadowEmulations.has(shadowRoot)) | 
 |  667     { | 
 |  668       let emulation = new ElemHideEmulation(this.document, | 
 |  669                                             shadowRoot, | 
 |  670                                             this.addSelectorsFunc, | 
 |  671                                             this.hideElemsFunc, | 
 |  672                                             this.shadowAttachedEventType); | 
 |  673       this.shadowEmulations.set(shadowRoot, emulation); | 
 |  674       emulation.apply(this.patterns, true); | 
 |  675     } | 
 |  676   }, | 
 |  677  | 
 |  678   findShadowRoots(nodes) | 
 |  679   { | 
 |  680     niceLoop(traverse(nodes), node => | 
 |  681     { | 
 |  682       let shadowRoot = node.shadowRoot; | 
 |  683       if (shadowRoot) | 
 |  684         this.addShadowRoot(shadowRoot); | 
 |  685     }); | 
 |  686   }, | 
 |  687  | 
|  568   observe(mutations) |  688   observe(mutations) | 
|  569   { |  689   { | 
 |  690     if (typeof ShadowRoot != "undefined") | 
 |  691     { | 
 |  692       // Find any preattached shadows. | 
 |  693       this.findShadowRoots(extractAddedSubtrees(mutations)); | 
 |  694     } | 
 |  695  | 
|  570     this.queueFiltering(); |  696     this.queueFiltering(); | 
|  571   }, |  697   }, | 
|  572  |  698  | 
|  573   apply(patterns) |  699   apply(patterns, parsed) | 
|  574   { |  700   { | 
|  575     this.patterns = []; |  701     if (parsed) | 
|  576     for (let pattern of patterns) |  | 
|  577     { |  702     { | 
|  578       let selectors = this.parseSelector(pattern.selector); |  703       this.patterns = patterns; | 
|  579       if (selectors != null && selectors.length > 0) |  704     } | 
|  580         this.patterns.push({selectors, text: pattern.text}); |  705     else | 
 |  706     { | 
 |  707       this.patterns = []; | 
 |  708       for (let pattern of patterns) | 
 |  709       { | 
 |  710         let selectors = this.parseSelector(pattern.selector); | 
 |  711         if (selectors != null && selectors.length > 0) | 
 |  712           this.patterns.push({selectors, text: pattern.text}); | 
 |  713       } | 
|  581     } |  714     } | 
|  582  |  715  | 
|  583     if (this.patterns.length > 0) |  716     if (this.patterns.length > 0) | 
|  584     { |  717     { | 
|  585       this.queueFiltering(); |  718       this.queueFiltering(); | 
|  586       this.observer.observe( |  719       this.observer.observe( | 
|  587         this.document, |  720         this.root, | 
|  588         { |  721         { | 
|  589           childList: true, |  722           childList: true, | 
|  590           attributes: true, |  723           attributes: true, | 
|  591           characterData: true, |  724           characterData: true, | 
|  592           subtree: true |  725           subtree: true | 
|  593         } |  726         } | 
|  594       ); |  727       ); | 
|  595       this.document.addEventListener("load", this.onLoad.bind(this), true); |  | 
|  596     } |  728     } | 
|  597   } |  729   } | 
|  598 }; |  730 }; | 
|  599  |  731  | 
|  600 exports.ElemHideEmulation = ElemHideEmulation; |  732 exports.ElemHideEmulation = ElemHideEmulation; | 
| OLD | NEW |