| LEFT | RIGHT | 
|---|
| 1 /* | 1 /* | 
| 2  * This file is part of Adblock Plus <http://adblockplus.org/>, | 2  * This file is part of Adblock Plus <https://adblockplus.org/>, | 
| 3  * Copyright (C) 2006-2014 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 SELECTOR_GROUP_SIZE = 20; | 18 var SELECTOR_GROUP_SIZE = 20; | 
| 19 | 19 | 
| 20 var typeMap = { | 20 var typeMap = { | 
| 21   "img": "IMAGE", | 21   "img": "IMAGE", | 
| 22   "input": "IMAGE", | 22   "input": "IMAGE", | 
|  | 23   "picture": "IMAGE", | 
| 23   "audio": "MEDIA", | 24   "audio": "MEDIA", | 
| 24   "video": "MEDIA", | 25   "video": "MEDIA", | 
| 25   "frame": "SUBDOCUMENT", | 26   "frame": "SUBDOCUMENT", | 
| 26   "iframe": "SUBDOCUMENT" | 27   "iframe": "SUBDOCUMENT", | 
|  | 28   "object": "OBJECT", | 
|  | 29   "embed": "OBJECT" | 
| 27 }; | 30 }; | 
|  | 31 | 
|  | 32 function getURLsFromObjectElement(element) | 
|  | 33 { | 
|  | 34   var url = element.getAttribute("data"); | 
|  | 35   if (url) | 
|  | 36     return [url]; | 
|  | 37 | 
|  | 38   for (var i = 0; i < element.children.length; i++) | 
|  | 39   { | 
|  | 40     var child = element.children[i]; | 
|  | 41     if (child.localName != "param") | 
|  | 42       continue; | 
|  | 43 | 
|  | 44     var name = child.getAttribute("name"); | 
|  | 45     if (name != "movie"  && // Adobe Flash | 
|  | 46         name != "source" && // Silverlight | 
|  | 47         name != "src"    && // Real Media + Quicktime | 
|  | 48         name != "FileName") // Windows Media | 
|  | 49       continue; | 
|  | 50 | 
|  | 51     var value = child.getAttribute("value"); | 
|  | 52     if (!value) | 
|  | 53       continue; | 
|  | 54 | 
|  | 55     return [value]; | 
|  | 56   } | 
|  | 57 | 
|  | 58   return []; | 
|  | 59 } | 
|  | 60 | 
|  | 61 function getURLsFromAttributes(element) | 
|  | 62 { | 
|  | 63   var urls = []; | 
|  | 64 | 
|  | 65   if (element.src) | 
|  | 66     urls.push(element.src); | 
|  | 67 | 
|  | 68   if (element.srcset) | 
|  | 69   { | 
|  | 70     var candidates = element.srcset.split(","); | 
|  | 71     for (var i = 0; i < candidates.length; i++) | 
|  | 72     { | 
|  | 73       var url = candidates[i].trim().replace(/\s+\S+$/, ""); | 
|  | 74       if (url) | 
|  | 75         urls.push(url); | 
|  | 76     } | 
|  | 77   } | 
|  | 78 | 
|  | 79   return urls; | 
|  | 80 } | 
|  | 81 | 
|  | 82 function getURLsFromMediaElement(element) | 
|  | 83 { | 
|  | 84   var urls = getURLsFromAttributes(element); | 
|  | 85 | 
|  | 86   for (var i = 0; i < element.children.length; i++) | 
|  | 87   { | 
|  | 88     var child = element.children[i]; | 
|  | 89     if (child.localName == "source" || child.localName == "track") | 
|  | 90       urls.push.apply(urls, getURLsFromAttributes(child)); | 
|  | 91   } | 
|  | 92 | 
|  | 93   if (element.poster) | 
|  | 94     urls.push(element.poster); | 
|  | 95 | 
|  | 96   return urls; | 
|  | 97 } | 
|  | 98 | 
|  | 99 function getURLsFromElement(element) | 
|  | 100 { | 
|  | 101   var urls; | 
|  | 102   switch (element.localName) | 
|  | 103   { | 
|  | 104     case "object": | 
|  | 105       urls = getURLsFromObjectElement(element); | 
|  | 106       break; | 
|  | 107 | 
|  | 108     case "video": | 
|  | 109     case "audio": | 
|  | 110     case "picture": | 
|  | 111       urls = getURLsFromMediaElement(element); | 
|  | 112       break; | 
|  | 113 | 
|  | 114     default: | 
|  | 115       urls = getURLsFromAttributes(element); | 
|  | 116       break; | 
|  | 117   } | 
|  | 118 | 
|  | 119   for (var i = 0; i < urls.length; i++) | 
|  | 120   { | 
|  | 121     if (/^(?!https?:)[\w-]+:/i.test(urls[i])) | 
|  | 122       urls.splice(i--, 1); | 
|  | 123   } | 
|  | 124 | 
|  | 125   return urls; | 
|  | 126 } | 
| 28 | 127 | 
| 29 function checkCollapse(element) | 128 function checkCollapse(element) | 
| 30 { | 129 { | 
| 31   var tag = element.localName; | 130   var tag = element.localName; | 
| 32   if (tag in typeMap) | 131   if (tag in typeMap) | 
| 33   { | 132   { | 
| 34     // This element failed loading, did we block it? | 133     // This element failed loading, did we block it? | 
| 35     var url = element.src; | 134     var urls = getURLsFromElement(element); | 
| 36     if (!url || !/^https?:/i.test(url)) | 135     if (urls.length == 0) | 
| 37       return; | 136       return; | 
| 38 | 137 | 
| 39     ext.backgroundPage.sendMessage( | 138     ext.backgroundPage.sendMessage( | 
| 40       { | 139       { | 
| 41         type: "should-collapse", | 140         type: "should-collapse", | 
| 42         url: url, | 141         urls: urls, | 
| 43         mediatype: typeMap[tag] | 142         mediatype: typeMap[tag], | 
|  | 143         baseURL: document.location.href | 
| 44       }, | 144       }, | 
| 45 | 145 | 
| 46       function(response) | 146       function(response) | 
| 47       { | 147       { | 
| 48         if (response && element.parentNode) | 148         if (response && element.parentNode) | 
| 49         { | 149         { | 
| 50           var property = "display"; | 150           var property = "display"; | 
| 51           var value = "none"; | 151           var value = "none"; | 
| 52 | 152 | 
| 53           // <frame> cannot be removed, doing that will mess up the frameset | 153           // <frame> cannot be removed, doing that will mess up the frameset | 
| 54           if (tag == "frame") | 154           if (tag == "frame") | 
| 55           { | 155           { | 
| 56             property = "visibility"; | 156             property = "visibility"; | 
| 57             value = "hidden"; | 157             value = "hidden"; | 
| 58           } | 158           } | 
| 59 | 159 | 
| 60           // <input type="image"> elements try to load their image again | 160           // <input type="image"> elements try to load their image again | 
| 61           // when the "display" CSS property is set. So we have to check | 161           // when the "display" CSS property is set. So we have to check | 
| 62           // that it isn't already collapsed to avoid an infinite recursion. | 162           // that it isn't already collapsed to avoid an infinite recursion. | 
| 63           if (element.style.getPropertyValue(property) != value || | 163           if (element.style.getPropertyValue(property) != value || | 
| 64               element.style.getPropertyPriority(property) != "important") | 164               element.style.getPropertyPriority(property) != "important") | 
| 65             element.style.setProperty(property, value, "important"); | 165             element.style.setProperty(property, value, "important"); | 
| 66         } | 166         } | 
| 67       } | 167       } | 
| 68     ); | 168     ); | 
| 69   } | 169   } | 
|  | 170 | 
|  | 171   window.collapsing = true; | 
| 70 } | 172 } | 
| 71 | 173 | 
| 72 function checkSitekey() | 174 function checkSitekey() | 
| 73 { | 175 { | 
| 74   var attr = document.documentElement.getAttribute("data-adblockkey"); | 176   var attr = document.documentElement.getAttribute("data-adblockkey"); | 
| 75   if (attr) | 177   if (attr) | 
| 76     ext.backgroundPage.sendMessage({type: "add-sitekey", token: attr}); | 178     ext.backgroundPage.sendMessage({type: "add-sitekey", token: attr}); | 
| 77 } | 179 } | 
| 78 | 180 | 
| 79 function hasInlineURL(element, attribute) | 181 function getContentDocument(element) | 
| 80 { | 182 { | 
| 81   var value = element.getAttribute(attribute); | 183   try | 
| 82   return value == null || /^\s*(javascript:|about:|$)/i.test(value); | 184   { | 
| 83 } | 185     return element.contentDocument; | 
| 84 | 186   } | 
| 85 function isInlineFrame(element) | 187   catch (e) | 
| 86 { | 188   { | 
| 87   switch (element.localName) | 189     return null; | 
| 88   { | 190   } | 
| 89     case "iframe": | 191 } | 
| 90       return hasInlineURL(element, "src") || element.hasAttribute("srcdoc"); | 192 | 
| 91     case "frame": | 193 function ElementHidingTracer(document, selectors) | 
| 92       return hasInlineURL(element, "src"); | 194 { | 
| 93     case "object": | 195   this.document = document; | 
| 94       return hasInlineURL(element, "data") && element.contentDocument; | 196   this.selectors = selectors; | 
| 95     default: | 197 | 
| 96       return false; | 198   this.changedNodes = []; | 
| 97   } | 199   this.timeout = null; | 
| 98 } | 200 | 
| 99 | 201   this.observer = new MutationObserver(this.observe.bind(this)); | 
| 100 function resolveURL(url) | 202   this.trace = this.trace.bind(this); | 
| 101 { | 203 | 
| 102   var a = document.createElement("a"); | 204   if (document.readyState == "loading") | 
| 103   a.href = url; | 205     document.addEventListener("DOMContentLoaded", this.trace); | 
| 104   return a.href; | 206   else | 
| 105 } | 207     this.trace(); | 
|  | 208 } | 
|  | 209 ElementHidingTracer.prototype = { | 
|  | 210   checkNodes: function(nodes) | 
|  | 211   { | 
|  | 212     var matchedSelectors = []; | 
|  | 213 | 
|  | 214     // Find all selectors that match any hidden element inside the given nodes. | 
|  | 215     for (var i = 0; i < this.selectors.length; i++) | 
|  | 216     { | 
|  | 217       var selector = this.selectors[i]; | 
|  | 218 | 
|  | 219       for (var j = 0; j < nodes.length; j++) | 
|  | 220       { | 
|  | 221         var elements = nodes[j].querySelectorAll(selector); | 
|  | 222         var matched = false; | 
|  | 223 | 
|  | 224         for (var k = 0; k < elements.length; k++) | 
|  | 225         { | 
|  | 226           // Only consider selectors that actually have an effect on the | 
|  | 227           // computed styles, and aren't overridden by rules with higher | 
|  | 228           // priority, or haven't been circumvented in a different way. | 
|  | 229           if (getComputedStyle(elements[k]).display == "none") | 
|  | 230           { | 
|  | 231             matchedSelectors.push(selector); | 
|  | 232             matched = true; | 
|  | 233             break; | 
|  | 234           } | 
|  | 235         } | 
|  | 236 | 
|  | 237         if (matched) | 
|  | 238           break; | 
|  | 239       } | 
|  | 240     } | 
|  | 241 | 
|  | 242     if (matchedSelectors.length > 0) | 
|  | 243       ext.backgroundPage.sendMessage({ | 
|  | 244         type: "trace-elemhide", | 
|  | 245         selectors: matchedSelectors | 
|  | 246       }); | 
|  | 247   }, | 
|  | 248 | 
|  | 249   onTimeout: function() | 
|  | 250   { | 
|  | 251     this.checkNodes(this.changedNodes); | 
|  | 252     this.changedNodes = []; | 
|  | 253     this.timeout = null; | 
|  | 254   }, | 
|  | 255 | 
|  | 256   observe: function(mutations) | 
|  | 257   { | 
|  | 258     // Forget previously changed nodes that are no longer in the DOM. | 
|  | 259     for (var i = 0; i < this.changedNodes.length; i++) | 
|  | 260     { | 
|  | 261       if (!this.document.contains(this.changedNodes[i])) | 
|  | 262         this.changedNodes.splice(i--, 1); | 
|  | 263     } | 
|  | 264 | 
|  | 265     for (var j = 0; j < mutations.length; j++) | 
|  | 266     { | 
|  | 267       var mutation = mutations[j]; | 
|  | 268       var node = mutation.target; | 
|  | 269 | 
|  | 270       // Ignore mutations of nodes that aren't in the DOM anymore. | 
|  | 271       if (!this.document.contains(node)) | 
|  | 272         continue; | 
|  | 273 | 
|  | 274       // Since querySelectorAll() doesn't consider the root itself | 
|  | 275       // and since CSS selectors can also match siblings, we have | 
|  | 276       // to consider the parent node for attribute mutations. | 
|  | 277       if (mutation.type == "attributes") | 
|  | 278         node = node.parentNode; | 
|  | 279 | 
|  | 280       var addNode = true; | 
|  | 281       for (var k = 0; k < this.changedNodes.length; k++) | 
|  | 282       { | 
|  | 283         var previouslyChangedNode = this.changedNodes[k]; | 
|  | 284 | 
|  | 285         // If we are already going to check an ancestor of this node, | 
|  | 286         // we can ignore this node, since it will be considered anyway | 
|  | 287         // when checking one of its ancestors. | 
|  | 288         if (previouslyChangedNode.contains(node)) | 
|  | 289         { | 
|  | 290           addNode = false; | 
|  | 291           break; | 
|  | 292         } | 
|  | 293 | 
|  | 294         // If this node is an ancestor of a node that previously changed, | 
|  | 295         // we can ignore that node, since it will be considered anyway | 
|  | 296         // when checking one of its ancestors. | 
|  | 297         if (node.contains(previouslyChangedNode)) | 
|  | 298           this.changedNodes.splice(k--, 1); | 
|  | 299       } | 
|  | 300 | 
|  | 301       if (addNode) | 
|  | 302         this.changedNodes.push(node); | 
|  | 303     } | 
|  | 304 | 
|  | 305     // Check only nodes whose descendants have changed, and not more often | 
|  | 306     // than once a second. Otherwise large pages with a lot of DOM mutations | 
|  | 307     // (like YouTube) freeze when the devtools panel is active. | 
|  | 308     if (this.timeout == null) | 
|  | 309       this.timeout = setTimeout(this.onTimeout.bind(this), 1000); | 
|  | 310   }, | 
|  | 311 | 
|  | 312   trace: function() | 
|  | 313   { | 
|  | 314     this.checkNodes([this.document]); | 
|  | 315 | 
|  | 316     this.observer.observe( | 
|  | 317       this.document, | 
|  | 318       { | 
|  | 319         childList: true, | 
|  | 320         attributes: true, | 
|  | 321         subtree: true | 
|  | 322       } | 
|  | 323     ); | 
|  | 324   }, | 
|  | 325 | 
|  | 326   disconnect: function() | 
|  | 327   { | 
|  | 328     this.document.removeEventListener("DOMContentLoaded", this.trace); | 
|  | 329     this.observer.disconnect(); | 
|  | 330     clearTimeout(this.timeout); | 
|  | 331   } | 
|  | 332 }; | 
| 106 | 333 | 
| 107 function reinjectRulesWhenRemoved(document, style) | 334 function reinjectRulesWhenRemoved(document, style) | 
| 108 { | 335 { | 
| 109   var MutationObserver = window.MutationObserver || window.WebKitMutationObserve
     r; | 336   var MutationObserver = window.MutationObserver || window.WebKitMutationObserve
     r; | 
| 110   if (!MutationObserver) | 337   if (!MutationObserver) | 
| 111     return; | 338     return; | 
| 112 | 339 | 
| 113   var observer = new MutationObserver(function(mutations) | 340   var observer = new MutationObserver(function(mutations) | 
| 114   { | 341   { | 
| 115     var isStyleRemoved = false; | 342     var isStyleRemoved = false; | 
| (...skipping 11 matching lines...) Expand all  Loading... | 
| 127     observer.disconnect(); | 354     observer.disconnect(); | 
| 128 | 355 | 
| 129     var n = document.styleSheets.length; | 356     var n = document.styleSheets.length; | 
| 130     if (n == 0) | 357     if (n == 0) | 
| 131       return; | 358       return; | 
| 132 | 359 | 
| 133     var stylesheet = document.styleSheets[n - 1]; | 360     var stylesheet = document.styleSheets[n - 1]; | 
| 134     ext.backgroundPage.sendMessage( | 361     ext.backgroundPage.sendMessage( | 
| 135       {type: "get-selectors"}, | 362       {type: "get-selectors"}, | 
| 136 | 363 | 
| 137       function(selectors) | 364       function(response) | 
| 138       { | 365       { | 
|  | 366         var selectors = response.selectors; | 
| 139         while (selectors.length > 0) | 367         while (selectors.length > 0) | 
| 140         { | 368         { | 
| 141           var selector = selectors.splice(0, SELECTOR_GROUP_SIZE).join(", "); | 369           var selector = selectors.splice(0, SELECTOR_GROUP_SIZE).join(", "); | 
| 142 | 370 | 
| 143           // Using non-standard addRule() here. This is the only way | 371           // Using non-standard addRule() here. This is the only way | 
| 144           // to add rules at the end of a cross-origin stylesheet | 372           // to add rules at the end of a cross-origin stylesheet | 
| 145           // because we don't know how many rules are already in there | 373           // because we don't know how many rules are already in there | 
| 146           stylesheet.addRule(selector, "display: none !important;"); | 374           stylesheet.addRule(selector, "display: none !important;"); | 
| 147         } | 375         } | 
| 148       } | 376       } | 
| 149     ); | 377     ); | 
| 150   }); | 378   }); | 
| 151 | 379 | 
| 152   observer.observe(style.parentNode, {childList: true}); | 380   observer.observe(style.parentNode, {childList: true}); | 
| 153 } | 381   return observer; | 
| 154 | 382 } | 
| 155 function traceHiddenElements(document, selectors) | 383 | 
| 156 { | 384 function convertSelectorsForShadowDOM(selectors) | 
| 157   function check(element) | 385 { | 
| 158   { | 386   var result = []; | 
| 159     var matchedSelectors = []; | 387   var prefix = "::content "; | 
| 160 | 388 | 
| 161     for (var i = 0; i < selectors.length; i++) | 389   for (var i = 0; i < selectors.length; i++) | 
| 162     { | 390   { | 
| 163       var selector = selectors[i]; | 391     var selector = selectors[i]; | 
| 164       var elements = document.querySelectorAll(selector); | 392     if (selector.indexOf(",") == -1) | 
| 165 | 393     { | 
| 166       for (var j = 0; j < elements.length; j++) | 394       result.push(prefix + selector); | 
| 167       { | 395       continue; | 
| 168         if (getComputedStyle(elements[j]).display == "none") | 396     } | 
|  | 397 | 
|  | 398     var start = 0; | 
|  | 399     var sep = ""; | 
|  | 400     for (var j = 0; j < selector.length; j++) | 
|  | 401     { | 
|  | 402       var chr = selector[j]; | 
|  | 403       if (chr == "\\") | 
|  | 404         j++; | 
|  | 405       else if (chr == sep) | 
|  | 406         sep = ""; | 
|  | 407       else if (sep == "") | 
|  | 408       { | 
|  | 409         if (chr == '"' || chr == "'") | 
|  | 410           sep = chr; | 
|  | 411         else if (chr == ",") | 
| 169         { | 412         { | 
| 170           matchedSelectors.push(selector); | 413           result.push(prefix + selector.substring(start, j)); | 
| 171           break; | 414           start = j + 1; | 
| 172         } | 415         } | 
| 173       } | 416       } | 
| 174     } | 417     } | 
| 175 | 418 | 
| 176     if (matchedSelectors.length > 0) | 419     result.push(prefix + selector.substring(start)); | 
| 177       ext.backgroundPage.sendMessage({type: "trace-elemhide", selectors: matched
     Selectors}); | 420   } | 
| 178   } | 421 | 
| 179 | 422   return result; | 
| 180   function trace() |  | 
| 181   { |  | 
| 182     check(); |  | 
| 183 |  | 
| 184     var MutationObserver = window.MutationObserver || window.WebKitMutationObser
     ver; |  | 
| 185     if (MutationObserver) |  | 
| 186     { |  | 
| 187       new MutationObserver(check).observe(document, |  | 
| 188       { |  | 
| 189         childList: true, |  | 
| 190         attributes: true, |  | 
| 191         subtree: true |  | 
| 192       }); |  | 
| 193     } |  | 
| 194   } |  | 
| 195 |  | 
| 196   if (document.readyState == "loading") |  | 
| 197     document.addEventListener("DOMContentLoaded", trace); |  | 
| 198   else |  | 
| 199     trace(); |  | 
| 200 } | 423 } | 
| 201 | 424 | 
| 202 function init(document) | 425 function init(document) | 
| 203 { | 426 { | 
| 204   // use Shadow DOM if available to don't mess with web pages that |  | 
| 205   // rely on the order of their own <style> tags (#309). However we |  | 
| 206   // must not create the shadow root in the response callback passed |  | 
| 207   // to sendMessage(), otherwise Chrome breaks some websites (#450). |  | 
| 208   var shadow = null; | 427   var shadow = null; | 
| 209   if ("createShadowRoot" in document.documentElement) | 428   var style = null; | 
|  | 429   var observer = null; | 
|  | 430   var tracer = null; | 
|  | 431   var propertyFilters = new CSSPropertyFilters(window, addElemHideSelectors); | 
|  | 432 | 
|  | 433   // Use Shadow DOM if available to don't mess with web pages that rely on | 
|  | 434   // the order of their own <style> tags (#309). | 
|  | 435   // | 
|  | 436   // However, creating a shadow root breaks running CSS transitions. So we | 
|  | 437   // have to create the shadow root before transistions might start (#452). | 
|  | 438   // | 
|  | 439   // Also, using shadow DOM causes issues on some Google websites, | 
|  | 440   // including Google Docs and Gmail (#1770, #2602). | 
|  | 441   if ("createShadowRoot" in document.documentElement && !/\.google\.com$/.test(d
     ocument.domain)) | 
| 210   { | 442   { | 
| 211     shadow = document.documentElement.createShadowRoot(); | 443     shadow = document.documentElement.createShadowRoot(); | 
| 212     shadow.appendChild(document.createElement("shadow")); | 444     shadow.appendChild(document.createElement("shadow")); | 
| 213   } | 445   } | 
| 214 | 446 | 
| 215   // Sets the currently used CSS rules for elemhide filters | 447   function addElemHideSelectors(selectors) | 
| 216   var setElemhideCSSRules = function(response) | 448   { | 
| 217   { | 449     if (selectors.length == 0) | 
| 218     if (response.selectors.length == 0) |  | 
| 219       return; | 450       return; | 
| 220 | 451 | 
| 221     var selectors = response.selectors.slice(0); | 452     if (!style) | 
| 222     var style = document.createElement("style"); | 453     { | 
| 223     style.setAttribute("type", "text/css"); | 454       // Create <style> element lazily, only if we add styles. Add it to | 
| 224 | 455       // the shadow DOM if possible. Otherwise fallback to the <head> or | 
|  | 456       // <html> element. If we have injected a style element before that | 
|  | 457       // has been removed (the sheet property is null), create a new one. | 
|  | 458       style = document.createElement("style"); | 
|  | 459       (shadow || document.head || document.documentElement).appendChild(style); | 
|  | 460 | 
|  | 461       // It can happen that the frame already navigated to a different | 
|  | 462       // document while we were waiting for the background page to respond. | 
|  | 463       // In that case the sheet property will stay null, after addind the | 
|  | 464       // <style> element to the shadow DOM. | 
|  | 465       if (!style.sheet) | 
|  | 466         return; | 
|  | 467 | 
|  | 468       observer = reinjectRulesWhenRemoved(document, style); | 
|  | 469     } | 
|  | 470 | 
|  | 471     // If using shadow DOM, we have to add the ::content pseudo-element | 
|  | 472     // before each selector, in order to match elements within the | 
|  | 473     // insertion point. | 
| 225     if (shadow) | 474     if (shadow) | 
| 226     { | 475       selectors = convertSelectorsForShadowDOM(selectors); | 
| 227       shadow.appendChild(style); | 476 | 
| 228 | 477     // WebKit (and Blink?) apparently chokes when the selector list in a | 
| 229       for (var i = 0; i < selectors.length; i++) | 478     // CSS rule is huge. So we split the elemhide selectors into groups. | 
| 230         selectors[i] = "::content " + selectors[i]; | 479     while (selectors.length > 0) | 
| 231     } | 480     { | 
| 232     else | 481       var selector = selectors.splice(0, SELECTOR_GROUP_SIZE).join(", "); | 
| 233     { | 482       style.sheet.addRule(selector, "display: none !important;"); | 
| 234       // Try to insert the style into the <head> tag, inserting directly under t
     he | 483     } | 
| 235       // document root breaks dev tools functionality: | 484   }; | 
| 236       // http://code.google.com/p/chromium/issues/detail?id=178109 | 485 | 
| 237       (document.head || document.documentElement).appendChild(style); | 486   var updateStylesheet = function() | 
| 238     } | 487   { | 
| 239 | 488     var selectors = null; | 
| 240     var setRules = function() | 489     var CSSPropertyFiltersLoaded = false; | 
| 241     { | 490 | 
| 242       // The sheet property might not exist yet if the | 491     var checkLoaded = function() | 
| 243       // <style> element was created for a sub frame | 492     { | 
| 244       if (!style.sheet) | 493       if (!selectors || !CSSPropertyFiltersLoaded) | 
| 245       { |  | 
| 246         setTimeout(setRules, 0); |  | 
| 247         return; | 494         return; | 
| 248       } | 495 | 
| 249 | 496       if (observer) | 
| 250       // WebKit apparently chokes when the selector list in a CSS rule is huge. | 497         observer.disconnect(); | 
| 251       // So we split the elemhide selectors into groups. | 498       observer = null; | 
| 252       for (var i = 0; selectors.length > 0; i++) | 499 | 
| 253       { | 500       if (tracer) | 
| 254         var selector = selectors.splice(0, SELECTOR_GROUP_SIZE).join(", "); | 501         tracer.disconnect(); | 
| 255         style.sheet.insertRule(selector + " { display: none !important; }", i); | 502       tracer = null; | 
| 256       } | 503 | 
| 257 | 504       if (style && style.parentElement) | 
| 258       if (response.trace) | 505         style.parentElement.removeChild(style); | 
| 259         traceHiddenElements(document, response.selectors); | 506       style = null; | 
|  | 507 | 
|  | 508       addElemHideSelectors(selectors.selectors); | 
|  | 509       propertyFilters.apply(); | 
|  | 510 | 
|  | 511       if (selectors.trace) | 
|  | 512         tracer = new ElementHidingTracer(document, selectors.selectors); | 
| 260     }; | 513     }; | 
| 261 | 514 | 
| 262     setRules(); | 515     ext.backgroundPage.sendMessage({type: "get-selectors"}, function(response) | 
| 263     reinjectRulesWhenRemoved(document, style); | 516     { | 
|  | 517       selectors = response; | 
|  | 518       checkLoaded(); | 
|  | 519     }); | 
|  | 520 | 
|  | 521     propertyFilters.load(function() | 
|  | 522     { | 
|  | 523       CSSPropertyFiltersLoaded = true; | 
|  | 524       checkLoaded(); | 
|  | 525     }); | 
| 264   }; | 526   }; | 
|  | 527 | 
|  | 528   updateStylesheet(); | 
| 265 | 529 | 
| 266   document.addEventListener("error", function(event) | 530   document.addEventListener("error", function(event) | 
| 267   { | 531   { | 
| 268     checkCollapse(event.target); | 532     checkCollapse(event.target); | 
| 269   }, true); | 533   }, true); | 
| 270 | 534 | 
| 271   document.addEventListener("load", function(event) | 535   document.addEventListener("load", function(event) | 
| 272   { | 536   { | 
| 273     var element = event.target; | 537     var element = event.target; | 
| 274 | 538 | 
| 275     if (/^i?frame$/.test(element.localName)) | 539     if (/^i?frame$/.test(element.localName)) | 
| 276       checkCollapse(element); | 540       checkCollapse(element); | 
| 277 | 541 | 
| 278     // prior to Chrome 37, content scripts cannot run on about:blank, | 542     if (/\bChrome\//.test(navigator.userAgent)) | 
| 279     // about:srcdoc and javascript: URLs. Moreover, as of Chrome 40 | 543     { | 
| 280     // "load" and "error" events aren't dispatched there. So we have | 544       var contentDocument = getContentDocument(element); | 
| 281     // to apply element hiding and collapsing from the parent frame. | 545       if (contentDocument) | 
| 282     if (/\bChrome\//.test(navigator.userAgent) && isInlineFrame(element)) | 546       { | 
| 283     { | 547         var contentWindow = contentDocument.defaultView; | 
| 284       init(element.contentDocument); | 548         if (contentDocument instanceof contentWindow.HTMLDocument) | 
| 285 | 549         { | 
| 286       for (var tagName in typeMap) | 550           // Prior to Chrome 37, content scripts cannot run in | 
| 287         Array.prototype.forEach.call(element.contentDocument.getElementsByTagNam
     e(tagName), checkCollapse); | 551           // dynamically created frames. Also on Chrome 37-40 | 
|  | 552           // document_start content scripts (like this one) don't | 
|  | 553           // run either in those frames due to https://crbug.com/416907. | 
|  | 554           // So we have to apply element hiding from the parent frame. | 
|  | 555           if (!("init" in contentWindow)) | 
|  | 556             init(contentDocument); | 
|  | 557 | 
|  | 558           // Moreover, "load" and "error" events aren't dispatched for elements | 
|  | 559           // in dynamically created frames due to https://crbug.com/442107. | 
|  | 560           // So we also have to apply element collpasing from the parent frame. | 
|  | 561           if (!contentWindow.collapsing) | 
|  | 562             Array.prototype.forEach.call( | 
|  | 563               contentDocument.querySelectorAll(Object.keys(typeMap).join(",")), | 
|  | 564               checkCollapse | 
|  | 565             ); | 
|  | 566         } | 
|  | 567       } | 
| 288     } | 568     } | 
| 289   }, true); | 569   }, true); | 
| 290 | 570 | 
| 291   ext.backgroundPage.sendMessage({type: "get-selectors"}, setElemhideCSSRules); | 571   return updateStylesheet; | 
| 292 } | 572 } | 
| 293 | 573 | 
| 294 if (document instanceof HTMLDocument) | 574 if (document instanceof HTMLDocument) | 
| 295 { | 575 { | 
| 296   checkSitekey(); | 576   checkSitekey(); | 
| 297   init(document); | 577   window.updateStylesheet = init(document); | 
| 298 } | 578 } | 
| LEFT | RIGHT | 
|---|