| 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-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       var url = candidates[i].trim().replace(/\s+\S+$/, ""); | 71       let url = candidate.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   } | 
| (...skipping 12 matching lines...) Expand all  Loading... | 
| 201 | 200 | 
| 202   this.observer = new MutationObserver(this.observe.bind(this)); | 201   this.observer = new MutationObserver(this.observe.bind(this)); | 
| 203   this.trace = this.trace.bind(this); | 202   this.trace = this.trace.bind(this); | 
| 204 | 203 | 
| 205   if (document.readyState == "loading") | 204   if (document.readyState == "loading") | 
| 206     document.addEventListener("DOMContentLoaded", this.trace); | 205     document.addEventListener("DOMContentLoaded", this.trace); | 
| 207   else | 206   else | 
| 208     this.trace(); | 207     this.trace(); | 
| 209 } | 208 } | 
| 210 ElementHidingTracer.prototype = { | 209 ElementHidingTracer.prototype = { | 
| 211   checkNodes: function(nodes) | 210   checkNodes(nodes) | 
| 212   { | 211   { | 
| 213     var matchedSelectors = []; | 212     let matchedSelectors = []; | 
| 214 | 213 | 
| 215     // Find all selectors that match any hidden element inside the given nodes. | 214     // Find all selectors that match any hidden element inside the given nodes. | 
| 216     for (var i = 0; i < this.selectors.length; i++) | 215     for (let selector of this.selectors) | 
| 217     { | 216     { | 
| 218       var selector = this.selectors[i]; | 217       for (let node of nodes) | 
|  | 218       { | 
|  | 219         let elements = node.querySelectorAll(selector); | 
|  | 220         let matched = false; | 
| 219 | 221 | 
| 220       for (var j = 0; j < nodes.length; j++) | 222         for (let element of elements) | 
| 221       { |  | 
| 222         var elements = nodes[j].querySelectorAll(selector); |  | 
| 223         var matched = false; |  | 
| 224 |  | 
| 225         for (var k = 0; k < elements.length; k++) |  | 
| 226         { | 223         { | 
| 227           // Only consider selectors that actually have an effect on the | 224           // Only consider selectors that actually have an effect on the | 
| 228           // computed styles, and aren't overridden by rules with higher | 225           // computed styles, and aren't overridden by rules with higher | 
| 229           // priority, or haven't been circumvented in a different way. | 226           // priority, or haven't been circumvented in a different way. | 
| 230           if (getComputedStyle(elements[k]).display == "none") | 227           if (getComputedStyle(element).display == "none") | 
| 231           { | 228           { | 
| 232             matchedSelectors.push(selector); | 229             matchedSelectors.push(selector); | 
| 233             matched = true; | 230             matched = true; | 
| 234             break; | 231             break; | 
| 235           } | 232           } | 
| 236         } | 233         } | 
| 237 | 234 | 
| 238         if (matched) | 235         if (matched) | 
| 239           break; | 236           break; | 
| 240       } | 237       } | 
| 241     } | 238     } | 
| 242 | 239 | 
| 243     if (matchedSelectors.length > 0) | 240     if (matchedSelectors.length > 0) | 
| 244       ext.backgroundPage.sendMessage({ | 241       ext.backgroundPage.sendMessage({ | 
| 245         type: "devtools.traceElemHide", | 242         type: "devtools.traceElemHide", | 
| 246         selectors: matchedSelectors | 243         selectors: matchedSelectors | 
| 247       }); | 244       }); | 
| 248   }, | 245   }, | 
| 249 | 246 | 
| 250   onTimeout: function() | 247   onTimeout() | 
| 251   { | 248   { | 
| 252     this.checkNodes(this.changedNodes); | 249     this.checkNodes(this.changedNodes); | 
| 253     this.changedNodes = []; | 250     this.changedNodes = []; | 
| 254     this.timeout = null; | 251     this.timeout = null; | 
| 255   }, | 252   }, | 
| 256 | 253 | 
| 257   observe: function(mutations) | 254   observe(mutations) | 
| 258   { | 255   { | 
| 259     // Forget previously changed nodes that are no longer in the DOM. | 256     // Forget previously changed nodes that are no longer in the DOM. | 
| 260     for (var i = 0; i < this.changedNodes.length; i++) | 257     for (let i = 0; i < this.changedNodes.length; i++) | 
| 261     { | 258     { | 
| 262       if (!document.contains(this.changedNodes[i])) | 259       if (!document.contains(this.changedNodes[i])) | 
| 263         this.changedNodes.splice(i--, 1); | 260         this.changedNodes.splice(i--, 1); | 
| 264     } | 261     } | 
| 265 | 262 | 
| 266     for (var j = 0; j < mutations.length; j++) | 263     for (let mutation of mutations) | 
| 267     { | 264     { | 
| 268       var mutation = mutations[j]; | 265       let node = mutation.target; | 
| 269       var node = mutation.target; |  | 
| 270 | 266 | 
| 271       // Ignore mutations of nodes that aren't in the DOM anymore. | 267       // Ignore mutations of nodes that aren't in the DOM anymore. | 
| 272       if (!document.contains(node)) | 268       if (!document.contains(node)) | 
| 273         continue; | 269         continue; | 
| 274 | 270 | 
| 275       // Since querySelectorAll() doesn't consider the root itself | 271       // Since querySelectorAll() doesn't consider the root itself | 
| 276       // and since CSS selectors can also match siblings, we have | 272       // and since CSS selectors can also match siblings, we have | 
| 277       // to consider the parent node for attribute mutations. | 273       // to consider the parent node for attribute mutations. | 
| 278       if (mutation.type == "attributes") | 274       if (mutation.type == "attributes") | 
| 279         node = node.parentNode; | 275         node = node.parentNode; | 
| 280 | 276 | 
| 281       var addNode = true; | 277       let addNode = true; | 
| 282       for (var k = 0; k < this.changedNodes.length; k++) | 278       for (let i = 0; i < this.changedNodes.length; i++) | 
| 283       { | 279       { | 
| 284         var previouslyChangedNode = this.changedNodes[k]; | 280         let previouslyChangedNode = this.changedNodes[i]; | 
| 285 | 281 | 
| 286         // If we are already going to check an ancestor of this node, | 282         // If we are already going to check an ancestor of this node, | 
| 287         // we can ignore this node, since it will be considered anyway | 283         // we can ignore this node, since it will be considered anyway | 
| 288         // when checking one of its ancestors. | 284         // when checking one of its ancestors. | 
| 289         if (previouslyChangedNode.contains(node)) | 285         if (previouslyChangedNode.contains(node)) | 
| 290         { | 286         { | 
| 291           addNode = false; | 287           addNode = false; | 
| 292           break; | 288           break; | 
| 293         } | 289         } | 
| 294 | 290 | 
| 295         // If this node is an ancestor of a node that previously changed, | 291         // If this node is an ancestor of a node that previously changed, | 
| 296         // we can ignore that node, since it will be considered anyway | 292         // we can ignore that node, since it will be considered anyway | 
| 297         // when checking one of its ancestors. | 293         // when checking one of its ancestors. | 
| 298         if (node.contains(previouslyChangedNode)) | 294         if (node.contains(previouslyChangedNode)) | 
| 299           this.changedNodes.splice(k--, 1); | 295           this.changedNodes.splice(i--, 1); | 
| 300       } | 296       } | 
| 301 | 297 | 
| 302       if (addNode) | 298       if (addNode) | 
| 303         this.changedNodes.push(node); | 299         this.changedNodes.push(node); | 
| 304     } | 300     } | 
| 305 | 301 | 
| 306     // Check only nodes whose descendants have changed, and not more often | 302     // Check only nodes whose descendants have changed, and not more often | 
| 307     // than once a second. Otherwise large pages with a lot of DOM mutations | 303     // than once a second. Otherwise large pages with a lot of DOM mutations | 
| 308     // (like YouTube) freeze when the devtools panel is active. | 304     // (like YouTube) freeze when the devtools panel is active. | 
| 309     if (this.timeout == null) | 305     if (this.timeout == null) | 
| 310       this.timeout = setTimeout(this.onTimeout.bind(this), 1000); | 306       this.timeout = setTimeout(this.onTimeout.bind(this), 1000); | 
| 311   }, | 307   }, | 
| 312 | 308 | 
| 313   trace: function() | 309   trace() | 
| 314   { | 310   { | 
| 315     this.checkNodes([document]); | 311     this.checkNodes([document]); | 
| 316 | 312 | 
| 317     this.observer.observe( | 313     this.observer.observe( | 
| 318       document, | 314       document, | 
| 319       { | 315       { | 
| 320         childList: true, | 316         childList: true, | 
| 321         attributes: true, | 317         attributes: true, | 
| 322         subtree: true | 318         subtree: true | 
| 323       } | 319       } | 
| 324     ); | 320     ); | 
| 325   }, | 321   }, | 
| 326 | 322 | 
| 327   disconnect: function() | 323   disconnect() | 
| 328   { | 324   { | 
| 329     document.removeEventListener("DOMContentLoaded", this.trace); | 325     document.removeEventListener("DOMContentLoaded", this.trace); | 
| 330     this.observer.disconnect(); | 326     this.observer.disconnect(); | 
| 331     clearTimeout(this.timeout); | 327     clearTimeout(this.timeout); | 
| 332   } | 328   } | 
| 333 }; | 329 }; | 
| 334 | 330 | 
| 335 function runInPageContext(fn, arg) | 331 function runInPageContext(fn, arg) | 
| 336 { | 332 { | 
| 337   var script = document.createElement("script"); | 333   let script = document.createElement("script"); | 
| 338   script.type = "application/javascript"; | 334   script.type = "application/javascript"; | 
| 339   script.async = false; | 335   script.async = false; | 
| 340   script.textContent = "(" + fn + ")(" + JSON.stringify(arg) + ");"; | 336   script.textContent = "(" + fn + ")(" + JSON.stringify(arg) + ");"; | 
| 341   document.documentElement.appendChild(script); | 337   document.documentElement.appendChild(script); | 
| 342   document.documentElement.removeChild(script); | 338   document.documentElement.removeChild(script); | 
| 343 } | 339 } | 
| 344 | 340 | 
| 345 // Chrome doesn't allow us to intercept WebSockets[1], and therefore | 341 // Chrome doesn't allow us to intercept WebSockets[1], and therefore | 
| 346 // some ad networks are misusing them as a way to serve adverts and circumvent | 342 // some ad networks are misusing them as a way to serve adverts and circumvent | 
| 347 // us. As a workaround we wrap WebSocket, preventing blocked WebSocket | 343 // us. As a workaround we wrap WebSocket, preventing blocked WebSocket | 
| 348 // connections from being opened. | 344 // connections from being opened. | 
| 349 // [1] - https://bugs.chromium.org/p/chromium/issues/detail?id=129353 | 345 // [1] - https://bugs.chromium.org/p/chromium/issues/detail?id=129353 | 
| 350 function wrapWebSocket() | 346 function wrapWebSocket() | 
| 351 { | 347 { | 
| 352   var eventName = "abpws-" + Math.random().toString(36).substr(2); | 348   let eventName = "abpws-" + Math.random().toString(36).substr(2); | 
| 353 | 349 | 
| 354   document.addEventListener(eventName, function(event) | 350   document.addEventListener(eventName, event => | 
| 355   { | 351   { | 
| 356     ext.backgroundPage.sendMessage({ | 352     ext.backgroundPage.sendMessage({ | 
| 357       type: "request.websocket", | 353       type: "request.websocket", | 
| 358       url: event.detail.url | 354       url: event.detail.url | 
| 359     }, function (block) | 355     }, block => | 
| 360     { | 356     { | 
| 361       document.dispatchEvent( | 357       document.dispatchEvent( | 
| 362         new CustomEvent(eventName + "-" + event.detail.url, {detail: block}) | 358         new CustomEvent(eventName + "-" + event.detail.url, {detail: block}) | 
| 363       ); | 359       ); | 
| 364     }); | 360     }); | 
| 365   }); | 361   }); | 
| 366 | 362 | 
| 367   runInPageContext(function(eventName) | 363   runInPageContext(eventName => | 
| 368   { | 364   { | 
| 369     // As far as possible we must track everything we use that could be | 365     // As far as possible we must track everything we use that could be | 
| 370     // sabotaged by the website later in order to circumvent us. | 366     // sabotaged by the website later in order to circumvent us. | 
| 371     var RealWebSocket = WebSocket; | 367     let RealWebSocket = WebSocket; | 
| 372     var closeWebSocket = Function.prototype.call.bind(RealWebSocket.prototype.cl
     ose); | 368     let closeWebSocket = Function.prototype.call.bind(RealWebSocket.prototype.cl
     ose); | 
| 373     var addEventListener = document.addEventListener.bind(document); | 369     let addEventListener = document.addEventListener.bind(document); | 
| 374     var removeEventListener = document.removeEventListener.bind(document); | 370     let removeEventListener = document.removeEventListener.bind(document); | 
| 375     var dispatchEvent = document.dispatchEvent.bind(document); | 371     let dispatchEvent = document.dispatchEvent.bind(document); | 
| 376     var CustomEvent = window.CustomEvent; | 372     let CustomEvent = window.CustomEvent; | 
| 377 | 373 | 
| 378     function checkRequest(url, callback) | 374     function checkRequest(url, callback) | 
| 379     { | 375     { | 
| 380       var incomingEventName = eventName + "-" + url; | 376       let incomingEventName = eventName + "-" + url; | 
| 381       function listener(event) | 377       function listener(event) | 
| 382       { | 378       { | 
| 383         callback(event.detail); | 379         callback(event.detail); | 
| 384         removeEventListener(incomingEventName, listener); | 380         removeEventListener(incomingEventName, listener); | 
| 385       } | 381       } | 
| 386       addEventListener(incomingEventName, listener); | 382       addEventListener(incomingEventName, listener); | 
| 387 | 383 | 
| 388       dispatchEvent(new CustomEvent(eventName, { | 384       dispatchEvent(new CustomEvent(eventName, { | 
| 389         detail: {url: url} | 385         detail: {url: url} | 
| 390       })); | 386       })); | 
| 391     } | 387     } | 
| 392 | 388 | 
| 393     function WrappedWebSocket(url) | 389     function WrappedWebSocket(url) | 
| 394     { | 390     { | 
| 395       // Throw correct exceptions if the constructor is used improperly. | 391       // Throw correct exceptions if the constructor is used improperly. | 
| 396       if (!(this instanceof WrappedWebSocket)) return RealWebSocket(); | 392       if (!(this instanceof WrappedWebSocket)) return RealWebSocket(); | 
| 397       if (arguments.length < 1) return new RealWebSocket(); | 393       if (arguments.length < 1) return new RealWebSocket(); | 
| 398 | 394 | 
| 399       var websocket; | 395       let websocket; | 
| 400       if (arguments.length == 1) | 396       if (arguments.length == 1) | 
| 401         websocket = new RealWebSocket(url); | 397         websocket = new RealWebSocket(url); | 
| 402       else | 398       else | 
| 403         websocket = new RealWebSocket(url, arguments[1]); | 399         websocket = new RealWebSocket(url, arguments[1]); | 
| 404 | 400 | 
| 405       checkRequest(websocket.url, function(blocked) | 401       checkRequest(websocket.url, blocked => | 
| 406       { | 402       { | 
| 407         if (blocked) | 403         if (blocked) | 
| 408           closeWebSocket(websocket); | 404           closeWebSocket(websocket); | 
| 409       }); | 405       }); | 
| 410 | 406 | 
| 411       return websocket; | 407       return websocket; | 
| 412     } | 408     } | 
| 413     WrappedWebSocket.prototype = RealWebSocket.prototype; | 409     WrappedWebSocket.prototype = RealWebSocket.prototype; | 
| 414     WebSocket = WrappedWebSocket.bind(); | 410     WebSocket = WrappedWebSocket.bind(); | 
| 415     Object.defineProperties(WebSocket, { | 411     Object.defineProperties(WebSocket, { | 
| 416       CONNECTING: {value: RealWebSocket.CONNECTING, enumerable: true}, | 412       CONNECTING: {value: RealWebSocket.CONNECTING, enumerable: true}, | 
| 417       OPEN: {value: RealWebSocket.OPEN, enumerable: true}, | 413       OPEN: {value: RealWebSocket.OPEN, enumerable: true}, | 
| 418       CLOSING: {value: RealWebSocket.CLOSING, enumerable: true}, | 414       CLOSING: {value: RealWebSocket.CLOSING, enumerable: true}, | 
| 419       CLOSED: {value: RealWebSocket.CLOSED, enumerable: true}, | 415       CLOSED: {value: RealWebSocket.CLOSED, enumerable: true}, | 
| 420       prototype: {value: RealWebSocket.prototype} | 416       prototype: {value: RealWebSocket.prototype} | 
| 421     }); | 417     }); | 
| 422 | 418 | 
| 423     RealWebSocket.prototype.constructor = WebSocket; | 419     RealWebSocket.prototype.constructor = WebSocket; | 
| 424   }, eventName); | 420   }, eventName); | 
| 425 } | 421 } | 
| 426 | 422 | 
| 427 function ElemHide() | 423 function ElemHide() | 
| 428 { | 424 { | 
| 429   this.shadow = this.createShadowTree(); | 425   this.shadow = this.createShadowTree(); | 
| 430   this.style = null; | 426   this.style = null; | 
| 431   this.tracer = null; | 427   this.tracer = null; | 
| 432 | 428 | 
| 433   this.elemHideEmulation = new ElemHideEmulation( | 429   this.elemHideEmulation = new ElemHideEmulation( | 
| 434     window, | 430     window, | 
| 435     function(callback) | 431     callback => | 
| 436     { | 432     { | 
| 437       ext.backgroundPage.sendMessage({ | 433       ext.backgroundPage.sendMessage({ | 
| 438         type: "filters.get", | 434         type: "filters.get", | 
| 439         what: "elemhideemulation" | 435         what: "elemhideemulation" | 
| 440       }, callback); | 436       }, callback); | 
| 441     }, | 437     }, | 
| 442     this.addSelectors.bind(this) | 438     this.addSelectors.bind(this) | 
| 443   ); | 439   ); | 
| 444 } | 440 } | 
| 445 ElemHide.prototype = { | 441 ElemHide.prototype = { | 
| 446   selectorGroupSize: 200, | 442   selectorGroupSize: 200, | 
| 447 | 443 | 
| 448   createShadowTree: function() | 444   createShadowTree() | 
| 449   { | 445   { | 
| 450     // Use Shadow DOM if available as to not mess with with web pages that | 446     // Use Shadow DOM if available as to not mess with with web pages that | 
| 451     // rely on the order of their own <style> tags (#309). However, creating | 447     // rely on the order of their own <style> tags (#309). However, creating | 
| 452     // a shadow root breaks running CSS transitions. So we have to create | 448     // a shadow root breaks running CSS transitions. So we have to create | 
| 453     // the shadow root before transistions might start (#452). | 449     // the shadow root before transistions might start (#452). | 
| 454     if (!("createShadowRoot" in document.documentElement)) | 450     if (!("createShadowRoot" in document.documentElement)) | 
| 455       return null; | 451       return null; | 
| 456 | 452 | 
| 457     // Using shadow DOM causes issues on some Google websites, | 453     // Using shadow DOM causes issues on some Google websites, | 
| 458     // including Google Docs, Gmail and Blogger (#1770, #2602, #2687). | 454     // including Google Docs, Gmail and Blogger (#1770, #2602, #2687). | 
| 459     if (/\.(?:google|blogger)\.com$/.test(document.domain)) | 455     if (/\.(?:google|blogger)\.com$/.test(document.domain)) | 
| 460       return null; | 456       return null; | 
| 461 | 457 | 
| 462     // Finally since some users have both AdBlock and Adblock Plus installed we | 458     // Finally since some users have both AdBlock and Adblock Plus installed we | 
| 463     // have to consider how the two extensions interact. For example we want to | 459     // have to consider how the two extensions interact. For example we want to | 
| 464     // avoid creating the shadowRoot twice. | 460     // avoid creating the shadowRoot twice. | 
| 465     var shadow = document.documentElement.shadowRoot || | 461     let shadow = document.documentElement.shadowRoot || | 
| 466                  document.documentElement.createShadowRoot(); | 462                  document.documentElement.createShadowRoot(); | 
| 467     shadow.appendChild(document.createElement("shadow")); | 463     shadow.appendChild(document.createElement("shadow")); | 
| 468 | 464 | 
| 469     // Stop the website from messing with our shadow root (#4191, #4298). | 465     // Stop the website from messing with our shadow root (#4191, #4298). | 
| 470     if ("shadowRoot" in Element.prototype) | 466     if ("shadowRoot" in Element.prototype) | 
| 471     { | 467     { | 
| 472       runInPageContext(function() | 468       runInPageContext(() => | 
| 473       { | 469       { | 
| 474         var ourShadowRoot = document.documentElement.shadowRoot; | 470         let ourShadowRoot = document.documentElement.shadowRoot; | 
| 475         if (!ourShadowRoot) | 471         if (!ourShadowRoot) | 
| 476           return; | 472           return; | 
| 477         var desc = Object.getOwnPropertyDescriptor(Element.prototype, "shadowRoo
     t"); | 473         let desc = Object.getOwnPropertyDescriptor(Element.prototype, "shadowRoo
     t"); | 
| 478         var shadowRoot = Function.prototype.call.bind(desc.get); | 474         let shadowRoot = Function.prototype.call.bind(desc.get); | 
| 479 | 475 | 
| 480         Object.defineProperty(Element.prototype, "shadowRoot", { | 476         Object.defineProperty(Element.prototype, "shadowRoot", { | 
| 481           configurable: true, enumerable: true, get: function() | 477           configurable: true, enumerable: true, get() | 
| 482           { | 478           { | 
| 483             var shadow = shadowRoot(this); | 479             let shadow = shadowRoot(this); | 
| 484             return shadow == ourShadowRoot ? null : shadow; | 480             return shadow == ourShadowRoot ? null : shadow; | 
| 485           } | 481           } | 
| 486         }); | 482         }); | 
| 487       }, null); | 483       }, null); | 
| 488     } | 484     } | 
| 489 | 485 | 
| 490     return shadow; | 486     return shadow; | 
| 491   }, | 487   }, | 
| 492 | 488 | 
| 493   addSelectors: function(selectors) | 489   addSelectors(selectors) | 
| 494   { | 490   { | 
| 495     if (selectors.length == 0) | 491     if (selectors.length == 0) | 
| 496       return; | 492       return; | 
| 497 | 493 | 
| 498     if (!this.style) | 494     if (!this.style) | 
| 499     { | 495     { | 
| 500       // Create <style> element lazily, only if we add styles. Add it to | 496       // Create <style> element lazily, only if we add styles. Add it to | 
| 501       // the shadow DOM if possible. Otherwise fallback to the <head> or | 497       // the shadow DOM if possible. Otherwise fallback to the <head> or | 
| 502       // <html> element. If we have injected a style element before that | 498       // <html> element. If we have injected a style element before that | 
| 503       // has been removed (the sheet property is null), create a new one. | 499       // has been removed (the sheet property is null), create a new one. | 
| 504       this.style = document.createElement("style"); | 500       this.style = document.createElement("style"); | 
| 505       (this.shadow || document.head | 501       (this.shadow || document.head | 
| 506                    || document.documentElement).appendChild(this.style); | 502                    || document.documentElement).appendChild(this.style); | 
| 507 | 503 | 
| 508       // It can happen that the frame already navigated to a different | 504       // It can happen that the frame already navigated to a different | 
| 509       // document while we were waiting for the background page to respond. | 505       // document while we were waiting for the background page to respond. | 
| 510       // In that case the sheet property will stay null, after addind the | 506       // In that case the sheet property will stay null, after addind the | 
| 511       // <style> element to the shadow DOM. | 507       // <style> element to the shadow DOM. | 
| 512       if (!this.style.sheet) | 508       if (!this.style.sheet) | 
| 513         return; | 509         return; | 
| 514     } | 510     } | 
| 515 | 511 | 
| 516     // If using shadow DOM, we have to add the ::content pseudo-element | 512     // If using shadow DOM, we have to add the ::content pseudo-element | 
| 517     // before each selector, in order to match elements within the | 513     // before each selector, in order to match elements within the | 
| 518     // insertion point. | 514     // insertion point. | 
| 519     if (this.shadow) | 515     if (this.shadow) | 
| 520     { | 516     { | 
| 521       var preparedSelectors = []; | 517       let preparedSelectors = []; | 
| 522       for (var i = 0; i < selectors.length; i++) | 518       for (let selector of selectors) | 
| 523       { | 519       { | 
| 524         var subSelectors = splitSelector(selectors[i]); | 520         let subSelectors = splitSelector(selector); | 
| 525         for (var j = 0; j < subSelectors.length; j++) | 521         for (let subSelector of subSelectors) | 
| 526           preparedSelectors.push("::content " + subSelectors[j]); | 522           preparedSelectors.push("::content " + subSelector); | 
| 527       } | 523       } | 
| 528       selectors = preparedSelectors; | 524       selectors = preparedSelectors; | 
| 529     } | 525     } | 
| 530 | 526 | 
| 531     // Safari only allows 8192 primitive selectors to be injected at once[1], we | 527     // Safari only allows 8192 primitive selectors to be injected at once[1], we | 
| 532     // therefore chunk the inserted selectors into groups of 200 to be safe. | 528     // therefore chunk the inserted selectors into groups of 200 to be safe. | 
| 533     // (Chrome also has a limit, larger... but we're not certain exactly what it | 529     // (Chrome also has a limit, larger... but we're not certain exactly what it | 
| 534     //  is! Edge apparently has no such limit.) | 530     //  is! Edge apparently has no such limit.) | 
| 535     // [1] - https://github.com/WebKit/webkit/blob/1cb2227f6b2a1035f7bdc46e5ab69
     debb75fc1de/Source/WebCore/css/RuleSet.h#L68 | 531     // [1] - https://github.com/WebKit/webkit/blob/1cb2227f6b2a1035f7bdc46e5ab69
     debb75fc1de/Source/WebCore/css/RuleSet.h#L68 | 
| 536     for (var i = 0; i < selectors.length; i += this.selectorGroupSize) | 532     for (let i = 0; i < selectors.length; i += this.selectorGroupSize) | 
| 537     { | 533     { | 
| 538       var selector = selectors.slice(i, i + this.selectorGroupSize).join(", "); | 534       let selector = selectors.slice(i, i + this.selectorGroupSize).join(", "); | 
| 539       this.style.sheet.insertRule(selector + "{display: none !important;}", | 535       this.style.sheet.insertRule(selector + "{display: none !important;}", | 
| 540                                   this.style.sheet.cssRules.length); | 536                                   this.style.sheet.cssRules.length); | 
| 541     } | 537     } | 
| 542   }, | 538   }, | 
| 543 | 539 | 
| 544   apply: function() | 540   apply() | 
| 545   { | 541   { | 
| 546     var selectors = null; | 542     let selectors = null; | 
| 547     var elemHideEmulationLoaded = false; | 543     let elemHideEmulationLoaded = false; | 
| 548 | 544 | 
| 549     var checkLoaded = function() | 545     let checkLoaded = function() | 
| 550     { | 546     { | 
| 551       if (!selectors || !elemHideEmulationLoaded) | 547       if (!selectors || !elemHideEmulationLoaded) | 
| 552         return; | 548         return; | 
| 553 | 549 | 
| 554       if (this.tracer) | 550       if (this.tracer) | 
| 555         this.tracer.disconnect(); | 551         this.tracer.disconnect(); | 
| 556       this.tracer = null; | 552       this.tracer = null; | 
| 557 | 553 | 
| 558       if (this.style && this.style.parentElement) | 554       if (this.style && this.style.parentElement) | 
| 559         this.style.parentElement.removeChild(this.style); | 555         this.style.parentElement.removeChild(this.style); | 
| 560       this.style = null; | 556       this.style = null; | 
| 561 | 557 | 
| 562       this.addSelectors(selectors.selectors); | 558       this.addSelectors(selectors.selectors); | 
| 563       this.elemHideEmulation.apply(); | 559       this.elemHideEmulation.apply(); | 
| 564 | 560 | 
| 565       if (selectors.trace) | 561       if (selectors.trace) | 
| 566         this.tracer = new ElementHidingTracer(selectors.selectors); | 562         this.tracer = new ElementHidingTracer(selectors.selectors); | 
| 567     }.bind(this); | 563     }.bind(this); | 
| 568 | 564 | 
| 569     ext.backgroundPage.sendMessage({type: "get-selectors"}, function(response) | 565     ext.backgroundPage.sendMessage({type: "get-selectors"}, response => | 
| 570     { | 566     { | 
| 571       selectors = response; | 567       selectors = response; | 
| 572       checkLoaded(); | 568       checkLoaded(); | 
| 573     }); | 569     }); | 
| 574 | 570 | 
| 575     this.elemHideEmulation.load(function() | 571     this.elemHideEmulation.load(() => | 
| 576     { | 572     { | 
| 577       elemHideEmulationLoaded = true; | 573       elemHideEmulationLoaded = true; | 
| 578       checkLoaded(); | 574       checkLoaded(); | 
| 579     }); | 575     }); | 
| 580   } | 576   } | 
| 581 }; | 577 }; | 
| 582 | 578 | 
| 583 if (document instanceof HTMLDocument) | 579 if (document instanceof HTMLDocument) | 
| 584 { | 580 { | 
| 585   checkSitekey(); | 581   checkSitekey(); | 
| 586   wrapWebSocket(); | 582   wrapWebSocket(); | 
| 587 | 583 | 
|  | 584   // This variable is also used by our other content scripts, outside of the | 
|  | 585   // current scope. | 
| 588   var elemhide = new ElemHide(); | 586   var elemhide = new ElemHide(); | 
| 589   elemhide.apply(); | 587   elemhide.apply(); | 
| 590 | 588 | 
| 591   document.addEventListener("error", function(event) | 589   document.addEventListener("error", event => | 
| 592   { | 590   { | 
| 593     checkCollapse(event.target); | 591     checkCollapse(event.target); | 
| 594   }, true); | 592   }, true); | 
| 595 | 593 | 
| 596   document.addEventListener("load", function(event) | 594   document.addEventListener("load", event => | 
| 597   { | 595   { | 
| 598     var element = event.target; | 596     let element = event.target; | 
| 599     if (/^i?frame$/.test(element.localName)) | 597     if (/^i?frame$/.test(element.localName)) | 
| 600       checkCollapse(element); | 598       checkCollapse(element); | 
| 601   }, true); | 599   }, true); | 
| 602 } | 600 } | 
| OLD | NEW | 
|---|