| 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-2016 Eyeo GmbH |    3  * Copyright (C) 2006-2016 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 var typeMap = { |   18 "use strict"; | 
 |   19  | 
 |   20 const typeMap = { | 
|   19   "img": "IMAGE", |   21   "img": "IMAGE", | 
|   20   "input": "IMAGE", |   22   "input": "IMAGE", | 
|   21   "picture": "IMAGE", |   23   "picture": "IMAGE", | 
|   22   "audio": "MEDIA", |   24   "audio": "MEDIA", | 
|   23   "video": "MEDIA", |   25   "video": "MEDIA", | 
|   24   "frame": "SUBDOCUMENT", |   26   "frame": "SUBDOCUMENT", | 
|   25   "iframe": "SUBDOCUMENT", |   27   "iframe": "SUBDOCUMENT", | 
|   26   "object": "OBJECT", |   28   "object": "OBJECT", | 
|   27   "embed": "OBJECT" |   29   "embed": "OBJECT" | 
|   28 }; |   30 }; | 
|   29  |   31  | 
|   30 function getURLsFromObjectElement(element) |   32 function getURLsFromObjectElement(element) | 
|   31 { |   33 { | 
|   32   var url = element.getAttribute("data"); |   34   let url = element.getAttribute("data"); | 
|   33   if (url) |   35   if (url) | 
|   34     return [url]; |   36     return [url]; | 
|   35  |   37  | 
|   36   for (var i = 0; i < element.children.length; i++) |   38   for (let child of element.children) | 
|   37   { |   39   { | 
|   38     var child = element.children[i]; |  | 
|   39     if (child.localName != "param") |   40     if (child.localName != "param") | 
|   40       continue; |   41       continue; | 
|   41  |   42  | 
|   42     var name = child.getAttribute("name"); |   43     let name = child.getAttribute("name"); | 
|   43     if (name != "movie"  && // Adobe Flash |   44     if (name != "movie"  && // Adobe Flash | 
|   44         name != "source" && // Silverlight |   45         name != "source" && // Silverlight | 
|   45         name != "src"    && // Real Media + Quicktime |   46         name != "src"    && // Real Media + Quicktime | 
|   46         name != "FileName") // Windows Media |   47         name != "FileName") // Windows Media | 
|   47       continue; |   48       continue; | 
|   48  |   49  | 
|   49     var value = child.getAttribute("value"); |   50     let value = child.getAttribute("value"); | 
|   50     if (!value) |   51     if (!value) | 
|   51       continue; |   52       continue; | 
|   52  |   53  | 
|   53     return [value]; |   54     return [value]; | 
|   54   } |   55   } | 
|   55  |   56  | 
|   56   return []; |   57   return []; | 
|   57 } |   58 } | 
|   58  |   59  | 
|   59 function getURLsFromAttributes(element) |   60 function getURLsFromAttributes(element) | 
|   60 { |   61 { | 
|   61   var urls = []; |   62   let urls = []; | 
|   62  |   63  | 
|   63   if (element.src) |   64   if (element.src) | 
|   64     urls.push(element.src); |   65     urls.push(element.src); | 
|   65  |   66  | 
|   66   if (element.srcset) |   67   if (element.srcset) | 
|   67   { |   68   { | 
|   68     var candidates = element.srcset.split(","); |   69     for (let candidate of element.srcset.split(",")) | 
|   69     for (var i = 0; i < candidates.length; i++) |   70     { | 
|   70     { |   71       let url = candidate.trim().replace(/\s+\S+$/, ""); | 
|   71       var url = candidates[i].trim().replace(/\s+\S+$/, ""); |  | 
|   72       if (url) |   72       if (url) | 
|   73         urls.push(url); |   73         urls.push(url); | 
|   74     } |   74     } | 
|   75   } |   75   } | 
|   76  |   76  | 
|   77   return urls; |   77   return urls; | 
|   78 } |   78 } | 
|   79  |   79  | 
|   80 function getURLsFromMediaElement(element) |   80 function getURLsFromMediaElement(element) | 
|   81 { |   81 { | 
|   82   var urls = getURLsFromAttributes(element); |   82   let urls = getURLsFromAttributes(element); | 
|   83  |   83  | 
|   84   for (var i = 0; i < element.children.length; i++) |   84   for (let child of element.children) | 
|   85   { |   85   { | 
|   86     var child = element.children[i]; |  | 
|   87     if (child.localName == "source" || child.localName == "track") |   86     if (child.localName == "source" || child.localName == "track") | 
|   88       urls.push.apply(urls, getURLsFromAttributes(child)); |   87       urls.push.apply(urls, getURLsFromAttributes(child)); | 
|   89   } |   88   } | 
|   90  |   89  | 
|   91   if (element.poster) |   90   if (element.poster) | 
|   92     urls.push(element.poster); |   91     urls.push(element.poster); | 
|   93  |   92  | 
|   94   return urls; |   93   return urls; | 
|   95 } |   94 } | 
|   96  |   95  | 
|   97 function getURLsFromElement(element) |   96 function getURLsFromElement(element) | 
|   98 { |   97 { | 
|   99   var urls; |   98   let urls; | 
|  100   switch (element.localName) |   99   switch (element.localName) | 
|  101   { |  100   { | 
|  102     case "object": |  101     case "object": | 
|  103       urls = getURLsFromObjectElement(element); |  102       urls = getURLsFromObjectElement(element); | 
|  104       break; |  103       break; | 
|  105  |  104  | 
|  106     case "video": |  105     case "video": | 
|  107     case "audio": |  106     case "audio": | 
|  108     case "picture": |  107     case "picture": | 
|  109       urls = getURLsFromMediaElement(element); |  108       urls = getURLsFromMediaElement(element); | 
|  110       break; |  109       break; | 
|  111  |  110  | 
|  112     default: |  111     default: | 
|  113       urls = getURLsFromAttributes(element); |  112       urls = getURLsFromAttributes(element); | 
|  114       break; |  113       break; | 
|  115   } |  114   } | 
|  116  |  115  | 
|  117   for (var i = 0; i < urls.length; i++) |  116   for (let i = 0; i < urls.length; i++) | 
|  118   { |  117   { | 
|  119     if (/^(?!https?:)[\w-]+:/i.test(urls[i])) |  118     if (/^(?!https?:)[\w-]+:/i.test(urls[i])) | 
|  120       urls.splice(i--, 1); |  119       urls.splice(i--, 1); | 
|  121   } |  120   } | 
|  122  |  121  | 
|  123   return urls; |  122   return urls; | 
|  124 } |  123 } | 
|  125  |  124  | 
|  126 function checkCollapse(element) |  125 function checkCollapse(element) | 
|  127 { |  126 { | 
|  128   var mediatype = typeMap[element.localName]; |  127   let mediatype = typeMap[element.localName]; | 
|  129   if (!mediatype) |  128   if (!mediatype) | 
|  130     return; |  129     return; | 
|  131  |  130  | 
|  132   var urls = getURLsFromElement(element); |  131   let urls = getURLsFromElement(element); | 
|  133   if (urls.length == 0) |  132   if (urls.length == 0) | 
|  134     return; |  133     return; | 
|  135  |  134  | 
|  136   ext.backgroundPage.sendMessage( |  135   ext.backgroundPage.sendMessage( | 
|  137     { |  136     { | 
|  138       type: "filters.collapse", |  137       type: "filters.collapse", | 
|  139       urls: urls, |  138       urls: urls, | 
|  140       mediatype: mediatype, |  139       mediatype: mediatype, | 
|  141       baseURL: document.location.href |  140       baseURL: document.location.href | 
|  142     }, |  141     }, | 
|  143  |  142  | 
|  144     function(collapse) |  143     collapse => | 
|  145     { |  144     { | 
|  146       function collapseElement() |  145       function collapseElement() | 
|  147       { |  146       { | 
|  148         var propertyName = "display"; |  147         let propertyName = "display"; | 
|  149         var propertyValue = "none"; |  148         let propertyValue = "none"; | 
|  150         if (element.localName == "frame") |  149         if (element.localName == "frame") | 
|  151         { |  150         { | 
|  152           propertyName = "visibility"; |  151           propertyName = "visibility"; | 
|  153           propertyValue = "hidden"; |  152           propertyValue = "hidden"; | 
|  154         } |  153         } | 
|  155  |  154  | 
|  156         if (element.style.getPropertyValue(propertyName) != propertyValue || |  155         if (element.style.getPropertyValue(propertyName) != propertyValue || | 
|  157             element.style.getPropertyPriority(propertyName) != "important") |  156             element.style.getPropertyPriority(propertyName) != "important") | 
|  158           element.style.setProperty(propertyName, propertyValue, "important"); |  157           element.style.setProperty(propertyName, propertyValue, "important"); | 
|  159       } |  158       } | 
|  160  |  159  | 
|  161       if (collapse) |  160       if (collapse) | 
|  162       { |  161       { | 
|  163         collapseElement(); |  162         collapseElement(); | 
|  164  |  163  | 
|  165         new MutationObserver(collapseElement).observe( |  164         new MutationObserver(collapseElement).observe( | 
|  166           element, { |  165           element, { | 
|  167             attributes: true, |  166             attributes: true, | 
|  168             attributeFilter: ["style"] |  167             attributeFilter: ["style"] | 
|  169           } |  168           } | 
|  170         ); |  169         ); | 
|  171       } |  170       } | 
|  172     } |  171     } | 
|  173   ); |  172   ); | 
|  174 } |  173 } | 
|  175  |  174  | 
|  176 function checkSitekey() |  175 function checkSitekey() | 
|  177 { |  176 { | 
|  178   var attr = document.documentElement.getAttribute("data-adblockkey"); |  177   let attr = document.documentElement.getAttribute("data-adblockkey"); | 
|  179   if (attr) |  178   if (attr) | 
|  180     ext.backgroundPage.sendMessage({type: "filters.addKey", token: attr}); |  179     ext.backgroundPage.sendMessage({type: "filters.addKey", token: attr}); | 
|  181 } |  180 } | 
|  182  |  181  | 
|  183 function getContentDocument(element) |  182 function getContentDocument(element) | 
|  184 { |  183 { | 
|  185   try |  184   try | 
|  186   { |  185   { | 
|  187     return element.contentDocument; |  186     return element.contentDocument; | 
|  188   } |  187   } | 
|  189   catch (e) |  188   catch (e) | 
|  190   { |  189   { | 
|  191     return null; |  190     return null; | 
|  192   } |  191   } | 
|  193 } |  192 } | 
|  194  |  193  | 
|  195 function ElementHidingTracer() |  194 function ElementHidingTracer() | 
|  196 { |  195 { | 
|  197   this.selectors = []; |  196   this.selectors = []; | 
|  198   this.filters = []; |  197   this.filters = []; | 
|  199  |  198  | 
|  200   this.changedNodes = []; |  199   this.changedNodes = []; | 
|  201   this.timeout = null; |  200   this.timeout = null; | 
|  202   this.started = false; |  | 
|  203  |  201  | 
|  204   this.observer = new MutationObserver(this.observe.bind(this)); |  202   this.observer = new MutationObserver(this.observe.bind(this)); | 
|  205   this.trace = this.trace.bind(this); |  203   this.trace = this.trace.bind(this); | 
 |  204  | 
 |  205   if (document.readyState == "loading") | 
 |  206     document.addEventListener("DOMContentLoaded", this.trace); | 
 |  207   else | 
 |  208     this.trace(); | 
|  206 } |  209 } | 
|  207 ElementHidingTracer.prototype = { |  210 ElementHidingTracer.prototype = { | 
|  208   start: function() |  211   addSelectors(selectors, filters) | 
|  209   { |  212   { | 
|  210     if (document.readyState == "loading") |  213     if (document.readyState != "loading") | 
|  211       document.addEventListener("DOMContentLoaded", this.trace); |  214       this.checkNodes([document], selectors, filters); | 
|  212     else |  | 
|  213       this.trace(); |  | 
|  214   }, |  | 
|  215  |  | 
|  216  addSelectors: function(selectors, filters) |  | 
|  217   { |  | 
|  218     if (!filters) |  | 
|  219       filters = new Array(selectors.length); |  | 
|  220  |  | 
|  221     if (this.started) |  | 
|  222       window.setTimeout(() => { |  | 
|  223         this.checkNodes([document], selectors, filters); |  | 
|  224       }, 0); |  | 
|  225  |  215  | 
|  226     this.selectors.push(...selectors); |  216     this.selectors.push(...selectors); | 
|  227     this.filters.push(...filters); |  217     this.filters.push(...filters); | 
|  228   }, |  218   }, | 
|  229  |  219  | 
|  230   checkNodes: function(nodes, selectors, filters) |  220   checkNodes(nodes, selectors, filters) | 
|  231   { |  221   { | 
|  232     let matchedFilters = []; |  222     let matchedSelectors = []; | 
|  233  |  223  | 
|  234     for (let i = 0; i < selectors.length; i++) |  224     for (let i = 0; i < selectors.length; i++) | 
|  235     { |  225     { | 
|  236       nodes: for (let node of nodes) |  226       nodes: for (let node of nodes) | 
|  237       { |  227       { | 
|  238         let elements = node.querySelectorAll(selectors[i]); |  228         let elements = node.querySelectorAll(selectors[i]); | 
|  239  |  229  | 
|  240         for (let element of elements) |  230         for (let element of elements) | 
|  241         { |  231         { | 
|  242           // Only consider selectors that actually have an effect on the |  232           // Only consider selectors that actually have an effect on the | 
|  243           // computed styles, and aren't overridden by rules with higher |  233           // computed styles, and aren't overridden by rules with higher | 
|  244           // priority, or haven't been circumvented in a different way. |  234           // priority, or haven't been circumvented in a different way. | 
|  245           if (getComputedStyle(element).display == "none") |  235           if (getComputedStyle(element).display == "none") | 
|  246           { |  236           { | 
|  247             let filter = filters[i] || selectors[i]; |  237             matchedSelectors.push(filters[i].replace(/^.*?##/, "")); | 
|  248             matchedFilters.push(filter.replace(/^.*?##/, "")); |  | 
|  249             break nodes; |  238             break nodes; | 
|  250           } |  239           } | 
|  251         } |  240         } | 
|  252       } |  241       } | 
|  253     } |  242     } | 
|  254  |  243  | 
|  255     if (matchedFilters.length > 0) |  244     if (matchedSelectors.length > 0) | 
|  256       ext.backgroundPage.sendMessage({ |  245       ext.backgroundPage.sendMessage({ | 
|  257         type: "devtools.traceElemHide", |  246         type: "devtools.traceElemHide", | 
|  258         selectors: matchedFilters |  247         selectors: matchedSelectors | 
|  259       }); |  248       }); | 
|  260   }, |  249   }, | 
|  261  |  250  | 
|  262   onTimeout: function() |  251   onTimeout() | 
|  263   { |  252   { | 
|  264     this.checkNodes(this.changedNodes, this.selectors, this.filters); |  253     this.checkNodes(this.changedNodes, this.selectors, this.filters); | 
|  265     this.changedNodes = []; |  254     this.changedNodes = []; | 
|  266     this.timeout = null; |  255     this.timeout = null; | 
|  267   }, |  256   }, | 
|  268  |  257  | 
|  269   observe: function(mutations) |  258   observe(mutations) | 
|  270   { |  259   { | 
|  271     // Forget previously changed nodes that are no longer in the DOM. |  260     // Forget previously changed nodes that are no longer in the DOM. | 
|  272     for (var i = 0; i < this.changedNodes.length; i++) |  261     for (let i = 0; i < this.changedNodes.length; i++) | 
|  273     { |  262     { | 
|  274       if (!document.contains(this.changedNodes[i])) |  263       if (!document.contains(this.changedNodes[i])) | 
|  275         this.changedNodes.splice(i--, 1); |  264         this.changedNodes.splice(i--, 1); | 
|  276     } |  265     } | 
|  277  |  266  | 
|  278     for (var j = 0; j < mutations.length; j++) |  267     for (let mutation of mutations) | 
|  279     { |  268     { | 
|  280       var mutation = mutations[j]; |  269       let node = mutation.target; | 
|  281       var node = mutation.target; |  | 
|  282  |  270  | 
|  283       // Ignore mutations of nodes that aren't in the DOM anymore. |  271       // Ignore mutations of nodes that aren't in the DOM anymore. | 
|  284       if (!document.contains(node)) |  272       if (!document.contains(node)) | 
|  285         continue; |  273         continue; | 
|  286  |  274  | 
|  287       // Since querySelectorAll() doesn't consider the root itself |  275       // Since querySelectorAll() doesn't consider the root itself | 
|  288       // and since CSS selectors can also match siblings, we have |  276       // and since CSS selectors can also match siblings, we have | 
|  289       // to consider the parent node for attribute mutations. |  277       // to consider the parent node for attribute mutations. | 
|  290       if (mutation.type == "attributes") |  278       if (mutation.type == "attributes") | 
|  291         node = node.parentNode; |  279         node = node.parentNode; | 
|  292  |  280  | 
|  293       var addNode = true; |  281       let addNode = true; | 
|  294       for (var k = 0; k < this.changedNodes.length; k++) |  282       for (let i = 0; i < this.changedNodes.length; i++) | 
|  295       { |  283       { | 
|  296         var previouslyChangedNode = this.changedNodes[k]; |  284         let previouslyChangedNode = this.changedNodes[i]; | 
|  297  |  285  | 
|  298         // If we are already going to check an ancestor of this node, |  286         // If we are already going to check an ancestor of this node, | 
|  299         // we can ignore this node, since it will be considered anyway |  287         // we can ignore this node, since it will be considered anyway | 
|  300         // when checking one of its ancestors. |  288         // when checking one of its ancestors. | 
|  301         if (previouslyChangedNode.contains(node)) |  289         if (previouslyChangedNode.contains(node)) | 
|  302         { |  290         { | 
|  303           addNode = false; |  291           addNode = false; | 
|  304           break; |  292           break; | 
|  305         } |  293         } | 
|  306  |  294  | 
|  307         // If this node is an ancestor of a node that previously changed, |  295         // If this node is an ancestor of a node that previously changed, | 
|  308         // we can ignore that node, since it will be considered anyway |  296         // we can ignore that node, since it will be considered anyway | 
|  309         // when checking one of its ancestors. |  297         // when checking one of its ancestors. | 
|  310         if (node.contains(previouslyChangedNode)) |  298         if (node.contains(previouslyChangedNode)) | 
|  311           this.changedNodes.splice(k--, 1); |  299           this.changedNodes.splice(i--, 1); | 
|  312       } |  300       } | 
|  313  |  301  | 
|  314       if (addNode) |  302       if (addNode) | 
|  315         this.changedNodes.push(node); |  303         this.changedNodes.push(node); | 
|  316     } |  304     } | 
|  317  |  305  | 
|  318     // Check only nodes whose descendants have changed, and not more often |  306     // Check only nodes whose descendants have changed, and not more often | 
|  319     // than once a second. Otherwise large pages with a lot of DOM mutations |  307     // than once a second. Otherwise large pages with a lot of DOM mutations | 
|  320     // (like YouTube) freeze when the devtools panel is active. |  308     // (like YouTube) freeze when the devtools panel is active. | 
|  321     if (this.timeout == null) |  309     if (this.timeout == null) | 
|  322       this.timeout = setTimeout(this.onTimeout.bind(this), 1000); |  310       this.timeout = setTimeout(this.onTimeout.bind(this), 1000); | 
|  323   }, |  311   }, | 
|  324  |  312  | 
|  325   trace: function() |  313   trace() | 
|  326   { |  314   { | 
|  327     this.started = true; |  | 
|  328     this.checkNodes([document], this.selectors, this.filters); |  315     this.checkNodes([document], this.selectors, this.filters); | 
|  329  |  316  | 
|  330     this.observer.observe( |  317     this.observer.observe( | 
|  331       document, |  318       document, | 
|  332       { |  319       { | 
|  333         childList: true, |  320         childList: true, | 
|  334         attributes: true, |  321         attributes: true, | 
|  335         subtree: true |  322         subtree: true | 
|  336       } |  323       } | 
|  337     ); |  324     ); | 
|  338   }, |  325   }, | 
|  339  |  326  | 
|  340   disconnect: function() |  327   disconnect() | 
|  341   { |  328   { | 
|  342     document.removeEventListener("DOMContentLoaded", this.trace); |  329     document.removeEventListener("DOMContentLoaded", this.trace); | 
|  343     this.observer.disconnect(); |  330     this.observer.disconnect(); | 
|  344     clearTimeout(this.timeout); |  331     clearTimeout(this.timeout); | 
|  345   } |  332   } | 
|  346 }; |  333 }; | 
|  347  |  334  | 
|  348 function runInPageContext(fn, arg) |  335 function runInPageContext(fn, arg) | 
|  349 { |  336 { | 
|  350   var script = document.createElement("script"); |  337   let script = document.createElement("script"); | 
|  351   script.type = "application/javascript"; |  338   script.type = "application/javascript"; | 
|  352   script.async = false; |  339   script.async = false; | 
|  353   script.textContent = "(" + fn + ")(" + JSON.stringify(arg) + ");"; |  340   script.textContent = "(" + fn + ")(" + JSON.stringify(arg) + ");"; | 
|  354   document.documentElement.appendChild(script); |  341   document.documentElement.appendChild(script); | 
|  355   document.documentElement.removeChild(script); |  342   document.documentElement.removeChild(script); | 
|  356 } |  343 } | 
|  357  |  344  | 
|  358 // Chrome doesn't allow us to intercept WebSockets[1], and therefore |  345 // Chrome doesn't allow us to intercept WebSockets[1], and therefore | 
|  359 // some ad networks are misusing them as a way to serve adverts and circumvent |  346 // some ad networks are misusing them as a way to serve adverts and circumvent | 
|  360 // us. As a workaround we wrap WebSocket, preventing blocked WebSocket |  347 // us. As a workaround we wrap WebSocket, preventing blocked WebSocket | 
|  361 // connections from being opened. |  348 // connections from being opened. | 
|  362 // [1] - https://bugs.chromium.org/p/chromium/issues/detail?id=129353 |  349 // [1] - https://bugs.chromium.org/p/chromium/issues/detail?id=129353 | 
|  363 function wrapWebSocket() |  350 function wrapWebSocket() | 
|  364 { |  351 { | 
|  365   var eventName = "abpws-" + Math.random().toString(36).substr(2); |  352   let eventName = "abpws-" + Math.random().toString(36).substr(2); | 
|  366  |  353  | 
|  367   document.addEventListener(eventName, function(event) |  354   document.addEventListener(eventName, event => | 
|  368   { |  355   { | 
|  369     ext.backgroundPage.sendMessage({ |  356     ext.backgroundPage.sendMessage({ | 
|  370       type: "request.websocket", |  357       type: "request.websocket", | 
|  371       url: event.detail.url |  358       url: event.detail.url | 
|  372     }, function (block) |  359     }, block => | 
|  373     { |  360     { | 
|  374       document.dispatchEvent( |  361       document.dispatchEvent( | 
|  375         new CustomEvent(eventName + "-" + event.detail.url, {detail: block}) |  362         new CustomEvent(eventName + "-" + event.detail.url, {detail: block}) | 
|  376       ); |  363       ); | 
|  377     }); |  364     }); | 
|  378   }); |  365   }); | 
|  379  |  366  | 
|  380   runInPageContext(function(eventName) |  367   runInPageContext(eventName => | 
|  381   { |  368   { | 
|  382     // As far as possible we must track everything we use that could be |  369     // As far as possible we must track everything we use that could be | 
|  383     // sabotaged by the website later in order to circumvent us. |  370     // sabotaged by the website later in order to circumvent us. | 
|  384     var RealWebSocket = WebSocket; |  371     let RealWebSocket = WebSocket; | 
|  385     var closeWebSocket = Function.prototype.call.bind(RealWebSocket.prototype.cl
     ose); |  372     let closeWebSocket = Function.prototype.call.bind(RealWebSocket.prototype.cl
     ose); | 
|  386     var addEventListener = document.addEventListener.bind(document); |  373     let addEventListener = document.addEventListener.bind(document); | 
|  387     var removeEventListener = document.removeEventListener.bind(document); |  374     let removeEventListener = document.removeEventListener.bind(document); | 
|  388     var dispatchEvent = document.dispatchEvent.bind(document); |  375     let dispatchEvent = document.dispatchEvent.bind(document); | 
|  389     var CustomEvent = window.CustomEvent; |  376     let CustomEvent = window.CustomEvent; | 
|  390  |  377  | 
|  391     function checkRequest(url, callback) |  378     function checkRequest(url, callback) | 
|  392     { |  379     { | 
|  393       var incomingEventName = eventName + "-" + url; |  380       let incomingEventName = eventName + "-" + url; | 
|  394       function listener(event) |  381       function listener(event) | 
|  395       { |  382       { | 
|  396         callback(event.detail); |  383         callback(event.detail); | 
|  397         removeEventListener(incomingEventName, listener); |  384         removeEventListener(incomingEventName, listener); | 
|  398       } |  385       } | 
|  399       addEventListener(incomingEventName, listener); |  386       addEventListener(incomingEventName, listener); | 
|  400  |  387  | 
|  401       dispatchEvent(new CustomEvent(eventName, { |  388       dispatchEvent(new CustomEvent(eventName, { | 
|  402         detail: {url: url} |  389         detail: {url: url} | 
|  403       })); |  390       })); | 
|  404     } |  391     } | 
|  405  |  392  | 
|  406     function WrappedWebSocket(url) |  393     function WrappedWebSocket(url) | 
|  407     { |  394     { | 
|  408       // Throw correct exceptions if the constructor is used improperly. |  395       // Throw correct exceptions if the constructor is used improperly. | 
|  409       if (!(this instanceof WrappedWebSocket)) return RealWebSocket(); |  396       if (!(this instanceof WrappedWebSocket)) return RealWebSocket(); | 
|  410       if (arguments.length < 1) return new RealWebSocket(); |  397       if (arguments.length < 1) return new RealWebSocket(); | 
|  411  |  398  | 
|  412       var websocket; |  399       let websocket; | 
|  413       if (arguments.length == 1) |  400       if (arguments.length == 1) | 
|  414         websocket = new RealWebSocket(url); |  401         websocket = new RealWebSocket(url); | 
|  415       else |  402       else | 
|  416         websocket = new RealWebSocket(url, arguments[1]); |  403         websocket = new RealWebSocket(url, arguments[1]); | 
|  417  |  404  | 
|  418       checkRequest(websocket.url, function(blocked) |  405       checkRequest(websocket.url, blocked => | 
|  419       { |  406       { | 
|  420         if (blocked) |  407         if (blocked) | 
|  421           closeWebSocket(websocket); |  408           closeWebSocket(websocket); | 
|  422       }); |  409       }); | 
|  423  |  410  | 
|  424       return websocket; |  411       return websocket; | 
|  425     } |  412     } | 
|  426     WrappedWebSocket.prototype = RealWebSocket.prototype; |  413     WrappedWebSocket.prototype = RealWebSocket.prototype; | 
|  427     WebSocket = WrappedWebSocket.bind(); |  414     WebSocket = WrappedWebSocket.bind(); | 
|  428     Object.defineProperties(WebSocket, { |  415     Object.defineProperties(WebSocket, { | 
|  429       CONNECTING: {value: RealWebSocket.CONNECTING, enumerable: true}, |  416       CONNECTING: {value: RealWebSocket.CONNECTING, enumerable: true}, | 
|  430       OPEN: {value: RealWebSocket.OPEN, enumerable: true}, |  417       OPEN: {value: RealWebSocket.OPEN, enumerable: true}, | 
|  431       CLOSING: {value: RealWebSocket.CLOSING, enumerable: true}, |  418       CLOSING: {value: RealWebSocket.CLOSING, enumerable: true}, | 
|  432       CLOSED: {value: RealWebSocket.CLOSED, enumerable: true}, |  419       CLOSED: {value: RealWebSocket.CLOSED, enumerable: true}, | 
|  433       prototype: {value: RealWebSocket.prototype} |  420       prototype: {value: RealWebSocket.prototype} | 
|  434     }); |  421     }); | 
|  435  |  422  | 
|  436     RealWebSocket.prototype.constructor = WebSocket; |  423     RealWebSocket.prototype.constructor = WebSocket; | 
|  437   }, eventName); |  424   }, eventName); | 
|  438 } |  425 } | 
|  439  |  426  | 
|  440 function ElemHide() |  427 function ElemHide() | 
|  441 { |  428 { | 
|  442   this.shadow = this.createShadowTree(); |  429   this.shadow = this.createShadowTree(); | 
|  443   this.style = null; |  430   this.style = null; | 
|  444   this.tracer = null; |  431   this.tracer = null; | 
|  445  |  432  | 
|  446   this.elemHideEmulation = new ElemHideEmulation( |  433   this.elemHideEmulation = new ElemHideEmulation( | 
|  447     window, |  434     window, | 
|  448     function(callback) |  435     callback => | 
|  449     { |  436     { | 
|  450       ext.backgroundPage.sendMessage({ |  437       ext.backgroundPage.sendMessage({ | 
|  451         type: "filters.get", |  438         type: "filters.get", | 
|  452         what: "elemhideemulation" |  439         what: "elemhideemulation" | 
|  453       }, callback); |  440       }, callback); | 
|  454     }, |  441     }, | 
|  455     this.addSelectors.bind(this) |  442     this.addSelectors.bind(this) | 
|  456   ); |  443   ); | 
|  457 } |  444 } | 
|  458 ElemHide.prototype = { |  445 ElemHide.prototype = { | 
|  459   selectorGroupSize: 200, |  446   selectorGroupSize: 200, | 
|  460  |  447  | 
|  461   createShadowTree: function() |  448   createShadowTree() | 
|  462   { |  449   { | 
|  463     // Use Shadow DOM if available as to not mess with with web pages that |  450     // Use Shadow DOM if available as to not mess with with web pages that | 
|  464     // rely on the order of their own <style> tags (#309). However, creating |  451     // rely on the order of their own <style> tags (#309). However, creating | 
|  465     // a shadow root breaks running CSS transitions. So we have to create |  452     // a shadow root breaks running CSS transitions. So we have to create | 
|  466     // the shadow root before transistions might start (#452). |  453     // the shadow root before transistions might start (#452). | 
|  467     if (!("createShadowRoot" in document.documentElement)) |  454     if (!("createShadowRoot" in document.documentElement)) | 
|  468       return null; |  455       return null; | 
|  469  |  456  | 
|  470     // Using shadow DOM causes issues on some Google websites, |  457     // Using shadow DOM causes issues on some Google websites, | 
|  471     // including Google Docs, Gmail and Blogger (#1770, #2602, #2687). |  458     // including Google Docs, Gmail and Blogger (#1770, #2602, #2687). | 
|  472     if (/\.(?:google|blogger)\.com$/.test(document.domain)) |  459     if (/\.(?:google|blogger)\.com$/.test(document.domain)) | 
|  473       return null; |  460       return null; | 
|  474  |  461  | 
|  475     // Finally since some users have both AdBlock and Adblock Plus installed we |  462     // Finally since some users have both AdBlock and Adblock Plus installed we | 
|  476     // have to consider how the two extensions interact. For example we want to |  463     // have to consider how the two extensions interact. For example we want to | 
|  477     // avoid creating the shadowRoot twice. |  464     // avoid creating the shadowRoot twice. | 
|  478     var shadow = document.documentElement.shadowRoot || |  465     let shadow = document.documentElement.shadowRoot || | 
|  479                  document.documentElement.createShadowRoot(); |  466                  document.documentElement.createShadowRoot(); | 
|  480     shadow.appendChild(document.createElement("shadow")); |  467     shadow.appendChild(document.createElement("shadow")); | 
|  481  |  468  | 
|  482     // Stop the website from messing with our shadow root (#4191, #4298). |  469     // Stop the website from messing with our shadow root (#4191, #4298). | 
|  483     if ("shadowRoot" in Element.prototype) |  470     if ("shadowRoot" in Element.prototype) | 
|  484     { |  471     { | 
|  485       runInPageContext(function() |  472       runInPageContext(() => | 
|  486       { |  473       { | 
|  487         var ourShadowRoot = document.documentElement.shadowRoot; |  474         let ourShadowRoot = document.documentElement.shadowRoot; | 
|  488         if (!ourShadowRoot) |  475         if (!ourShadowRoot) | 
|  489           return; |  476           return; | 
|  490         var desc = Object.getOwnPropertyDescriptor(Element.prototype, "shadowRoo
     t"); |  477         let desc = Object.getOwnPropertyDescriptor(Element.prototype, "shadowRoo
     t"); | 
|  491         var shadowRoot = Function.prototype.call.bind(desc.get); |  478         let shadowRoot = Function.prototype.call.bind(desc.get); | 
|  492  |  479  | 
|  493         Object.defineProperty(Element.prototype, "shadowRoot", { |  480         Object.defineProperty(Element.prototype, "shadowRoot", { | 
|  494           configurable: true, enumerable: true, get: function() |  481           configurable: true, enumerable: true, get() | 
|  495           { |  482           { | 
|  496             var shadow = shadowRoot(this); |  483             let shadow = shadowRoot(this); | 
|  497             return shadow == ourShadowRoot ? null : shadow; |  484             return shadow == ourShadowRoot ? null : shadow; | 
|  498           } |  485           } | 
|  499         }); |  486         }); | 
|  500       }, null); |  487       }, null); | 
|  501     } |  488     } | 
|  502  |  489  | 
|  503     return shadow; |  490     return shadow; | 
|  504   }, |  491   }, | 
|  505  |  492  | 
|  506   addSelectors: function(selectors, filters) |  493   addSelectors(selectors, filters) | 
|  507   { |  494   { | 
|  508     if (!selectors.length) |  495     if (selectors.length == 0) | 
|  509       return; |  496       return; | 
|  510  |  | 
|  511     if (this.tracer) |  | 
|  512       this.tracer.addSelectors(selectors, filters); |  | 
|  513  |  497  | 
|  514     if (!this.style) |  498     if (!this.style) | 
|  515     { |  499     { | 
|  516       // Create <style> element lazily, only if we add styles. Add it to |  500       // Create <style> element lazily, only if we add styles. Add it to | 
|  517       // the shadow DOM if possible. Otherwise fallback to the <head> or |  501       // the shadow DOM if possible. Otherwise fallback to the <head> or | 
|  518       // <html> element. If we have injected a style element before that |  502       // <html> element. If we have injected a style element before that | 
|  519       // has been removed (the sheet property is null), create a new one. |  503       // has been removed (the sheet property is null), create a new one. | 
|  520       this.style = document.createElement("style"); |  504       this.style = document.createElement("style"); | 
|  521       (this.shadow || document.head |  505       (this.shadow || document.head | 
|  522                    || document.documentElement).appendChild(this.style); |  506                    || document.documentElement).appendChild(this.style); | 
|  523  |  507  | 
|  524       // It can happen that the frame already navigated to a different |  508       // It can happen that the frame already navigated to a different | 
|  525       // document while we were waiting for the background page to respond. |  509       // document while we were waiting for the background page to respond. | 
|  526       // In that case the sheet property will stay null, after addind the |  510       // In that case the sheet property will stay null, after addind the | 
|  527       // <style> element to the shadow DOM. |  511       // <style> element to the shadow DOM. | 
|  528       if (!this.style.sheet) |  512       if (!this.style.sheet) | 
|  529         return; |  513         return; | 
|  530     } |  514     } | 
|  531  |  515  | 
|  532     // If using shadow DOM, we have to add the ::content pseudo-element |  516     // If using shadow DOM, we have to add the ::content pseudo-element | 
|  533     // before each selector, in order to match elements within the |  517     // before each selector, in order to match elements within the | 
|  534     // insertion point. |  518     // insertion point. | 
 |  519     let preparedSelectors = []; | 
|  535     if (this.shadow) |  520     if (this.shadow) | 
|  536     { |  521     { | 
|  537       var preparedSelectors = []; |  522       for (let selector of selectors) | 
|  538       for (var i = 0; i < selectors.length; i++) |  523       { | 
|  539       { |  524         let subSelectors = splitSelector(selector); | 
|  540         var subSelectors = splitSelector(selectors[i]); |  525         for (let subSelector of subSelectors) | 
|  541         for (var j = 0; j < subSelectors.length; j++) |  526           preparedSelectors.push("::content " + subSelector); | 
|  542           preparedSelectors.push("::content " + subSelectors[j]); |  527       } | 
|  543       } |  528     } | 
|  544       selectors = preparedSelectors; |  529     else | 
 |  530     { | 
 |  531       preparedSelectors = selectors; | 
|  545     } |  532     } | 
|  546  |  533  | 
|  547     // Safari only allows 8192 primitive selectors to be injected at once[1], we |  534     // Safari only allows 8192 primitive selectors to be injected at once[1], we | 
|  548     // therefore chunk the inserted selectors into groups of 200 to be safe. |  535     // therefore chunk the inserted selectors into groups of 200 to be safe. | 
|  549     // (Chrome also has a limit, larger... but we're not certain exactly what it |  536     // (Chrome also has a limit, larger... but we're not certain exactly what it | 
|  550     //  is! Edge apparently has no such limit.) |  537     //  is! Edge apparently has no such limit.) | 
|  551     // [1] - https://github.com/WebKit/webkit/blob/1cb2227f6b2a1035f7bdc46e5ab69
     debb75fc1de/Source/WebCore/css/RuleSet.h#L68 |  538     // [1] - https://github.com/WebKit/webkit/blob/1cb2227f6b2a1035f7bdc46e5ab69
     debb75fc1de/Source/WebCore/css/RuleSet.h#L68 | 
|  552     for (var i = 0; i < selectors.length; i += this.selectorGroupSize) |  539     for (let i = 0; i < preparedSelectors.length; i += this.selectorGroupSize) | 
|  553     { |  540     { | 
|  554       var selector = selectors.slice(i, i + this.selectorGroupSize).join(", "); |  541       let selector = preparedSelectors.slice(i, i + this.selectorGroupSize).join
     (", "); | 
|  555       this.style.sheet.insertRule(selector + "{display: none !important;}", |  542       this.style.sheet.insertRule(selector + "{display: none !important;}", | 
|  556                                   this.style.sheet.cssRules.length); |  543                                   this.style.sheet.cssRules.length); | 
|  557     } |  544     } | 
|  558   }, |  545  | 
|  559  |  546     if (this.tracer) | 
|  560   apply: function() |  547       this.tracer.addSelectors(selectors, filters || selectors); | 
|  561   { |  548   }, | 
|  562     var selectors = null; |  549  | 
|  563  |  550   apply() | 
|  564     var checkLoaded = function() |  551   { | 
 |  552     ext.backgroundPage.sendMessage({type: "get-selectors"}, response => | 
|  565     { |  553     { | 
|  566       if (this.tracer) |  554       if (this.tracer) | 
|  567         this.tracer.disconnect(); |  555         this.tracer.disconnect(); | 
|  568       this.tracer = null; |  556       this.tracer = null; | 
|  569  |  557  | 
|  570       if (selectors.trace) |  | 
|  571         this.tracer = new ElementHidingTracer(); |  | 
|  572  |  | 
|  573       if (this.style && this.style.parentElement) |  558       if (this.style && this.style.parentElement) | 
|  574         this.style.parentElement.removeChild(this.style); |  559         this.style.parentElement.removeChild(this.style); | 
|  575       this.style = null; |  560       this.style = null; | 
|  576  |  561  | 
|  577       if (selectors.selectors) |  562       if (response.trace) | 
|  578         this.addSelectors(selectors.selectors); |  563         this.tracer = new ElementHidingTracer(); | 
|  579  |  564  | 
 |  565       this.addSelectors(response.selectors); | 
|  580       this.elemHideEmulation.apply(); |  566       this.elemHideEmulation.apply(); | 
|  581  |  | 
|  582       if (this.tracer) |  | 
|  583         this.tracer.start(); |  | 
|  584     }.bind(this); |  | 
|  585  |  | 
|  586     ext.backgroundPage.sendMessage({type: "get-selectors"}, response => |  | 
|  587     { |  | 
|  588       selectors = response; |  | 
|  589       this.elemHideEmulation.load(checkLoaded); |  | 
|  590     }); |  567     }); | 
|  591   } |  568   } | 
|  592 }; |  569 }; | 
|  593  |  570  | 
|  594 if (document instanceof HTMLDocument) |  571 if (document instanceof HTMLDocument) | 
|  595 { |  572 { | 
|  596   checkSitekey(); |  573   checkSitekey(); | 
|  597   wrapWebSocket(); |  574   wrapWebSocket(); | 
|  598  |  575  | 
 |  576   // This variable is also used by our other content scripts, outside of the | 
 |  577   // current scope. | 
|  599   var elemhide = new ElemHide(); |  578   var elemhide = new ElemHide(); | 
|  600   elemhide.apply(); |  579   elemhide.apply(); | 
|  601  |  580  | 
|  602   document.addEventListener("error", function(event) |  581   document.addEventListener("error", event => | 
|  603   { |  582   { | 
|  604     checkCollapse(event.target); |  583     checkCollapse(event.target); | 
|  605   }, true); |  584   }, true); | 
|  606  |  585  | 
|  607   document.addEventListener("load", function(event) |  586   document.addEventListener("load", event => | 
|  608   { |  587   { | 
|  609     var element = event.target; |  588     let element = event.target; | 
|  610     if (/^i?frame$/.test(element.localName)) |  589     if (/^i?frame$/.test(element.localName)) | 
|  611       checkCollapse(element); |  590       checkCollapse(element); | 
|  612   }, true); |  591   }, true); | 
|  613 } |  592 } | 
| LEFT | RIGHT |