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-2015 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(); |
106 | 208 } |
107 function traceHiddenElements(document, selectors) | 209 ElementHidingTracer.prototype = { |
108 { | 210 checkNodes: function(nodes) |
109 function check(element) | |
110 { | 211 { |
111 var matchedSelectors = []; | 212 var matchedSelectors = []; |
112 | 213 |
113 for (var i = 0; i < selectors.length; i++) | 214 // Find all selectors that match any hidden element inside the given nodes. |
114 { | 215 for (var i = 0; i < this.selectors.length; i++) |
115 var selector = selectors[i]; | 216 { |
116 var elements = document.querySelectorAll(selector); | 217 var selector = this.selectors[i]; |
117 | 218 |
118 for (var j = 0; j < elements.length; j++) | 219 for (var j = 0; j < nodes.length; j++) |
119 { | 220 { |
120 if (getComputedStyle(elements[j]).display == "none") | 221 var elements = nodes[j].querySelectorAll(selector); |
| 222 var matched = false; |
| 223 |
| 224 for (var k = 0; k < elements.length; k++) |
121 { | 225 { |
122 matchedSelectors.push(selector); | 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; |
123 break; | 291 break; |
124 } | 292 } |
125 } | 293 |
126 } | 294 // If this node is an ancestor of a node that previously changed, |
127 | 295 // we can ignore that node, since it will be considered anyway |
128 if (matchedSelectors.length > 0) | 296 // when checking one of its ancestors. |
129 ext.backgroundPage.sendMessage({type: "trace-elemhide", selectors: matched
Selectors}); | 297 if (node.contains(previouslyChangedNode)) |
130 } | 298 this.changedNodes.splice(k--, 1); |
131 | 299 } |
132 function trace() | 300 |
133 { | 301 if (addNode) |
134 check(); | 302 this.changedNodes.push(node); |
135 | 303 } |
136 new MutationObserver(check).observe( | 304 |
137 document, | 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, |
138 { | 318 { |
139 childList: true, | 319 childList: true, |
140 attributes: true, | 320 attributes: true, |
141 subtree: true | 321 subtree: true |
142 } | 322 } |
143 ); | 323 ); |
144 } | 324 }, |
145 | 325 |
146 if (document.readyState == "loading") | 326 disconnect: function() |
147 document.addEventListener("DOMContentLoaded", trace); | 327 { |
148 else | 328 this.document.removeEventListener("DOMContentLoaded", this.trace); |
149 trace(); | 329 this.observer.disconnect(); |
150 } | 330 clearTimeout(this.timeout); |
| 331 } |
| 332 }; |
151 | 333 |
152 function reinjectRulesWhenRemoved(document, style) | 334 function reinjectRulesWhenRemoved(document, style) |
153 { | 335 { |
154 var MutationObserver = window.MutationObserver || window.WebKitMutationObserve
r; | 336 var MutationObserver = window.MutationObserver || window.WebKitMutationObserve
r; |
155 if (!MutationObserver) | 337 if (!MutationObserver) |
156 return; | 338 return; |
157 | 339 |
158 var observer = new MutationObserver(function(mutations) | 340 var observer = new MutationObserver(function(mutations) |
159 { | 341 { |
160 var isStyleRemoved = false; | 342 var isStyleRemoved = false; |
(...skipping 11 matching lines...) Expand all Loading... |
172 observer.disconnect(); | 354 observer.disconnect(); |
173 | 355 |
174 var n = document.styleSheets.length; | 356 var n = document.styleSheets.length; |
175 if (n == 0) | 357 if (n == 0) |
176 return; | 358 return; |
177 | 359 |
178 var stylesheet = document.styleSheets[n - 1]; | 360 var stylesheet = document.styleSheets[n - 1]; |
179 ext.backgroundPage.sendMessage( | 361 ext.backgroundPage.sendMessage( |
180 {type: "get-selectors"}, | 362 {type: "get-selectors"}, |
181 | 363 |
182 function(selectors) | 364 function(response) |
183 { | 365 { |
| 366 var selectors = response.selectors; |
184 while (selectors.length > 0) | 367 while (selectors.length > 0) |
185 { | 368 { |
186 var selector = selectors.splice(0, SELECTOR_GROUP_SIZE).join(", "); | 369 var selector = selectors.splice(0, SELECTOR_GROUP_SIZE).join(", "); |
187 | 370 |
188 // Using non-standard addRule() here. This is the only way | 371 // Using non-standard addRule() here. This is the only way |
189 // to add rules at the end of a cross-origin stylesheet | 372 // to add rules at the end of a cross-origin stylesheet |
190 // 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 |
191 stylesheet.addRule(selector, "display: none !important;"); | 374 stylesheet.addRule(selector, "display: none !important;"); |
192 } | 375 } |
193 } | 376 } |
194 ); | 377 ); |
195 }); | 378 }); |
196 | 379 |
197 observer.observe(style.parentNode, {childList: true}); | 380 observer.observe(style.parentNode, {childList: true}); |
| 381 return observer; |
198 } | 382 } |
199 | 383 |
200 function convertSelectorsForShadowDOM(selectors) | 384 function convertSelectorsForShadowDOM(selectors) |
201 { | 385 { |
202 var result = []; | 386 var result = []; |
203 var prefix = "::content "; | 387 var prefix = "::content "; |
204 | 388 |
205 for (var i = 0; i < selectors.length; i++) | 389 for (var i = 0; i < selectors.length; i++) |
206 { | 390 { |
207 var selector = selectors[i]; | 391 var selector = selectors[i]; |
| 392 if (selector.indexOf(",") == -1) |
| 393 { |
| 394 result.push(prefix + selector); |
| 395 continue; |
| 396 } |
| 397 |
208 var start = 0; | 398 var start = 0; |
209 var sep = ""; | 399 var sep = ""; |
210 | |
211 for (var j = 0; j < selector.length; j++) | 400 for (var j = 0; j < selector.length; j++) |
212 { | 401 { |
213 var chr = selector[j]; | 402 var chr = selector[j]; |
214 if (chr == "\\") | 403 if (chr == "\\") |
215 j++; | 404 j++; |
216 else if (chr == sep) | 405 else if (chr == sep) |
217 sep = ""; | 406 sep = ""; |
218 else if (chr == '"' || chr == "'") | 407 else if (sep == "") |
219 sep = chr; | 408 { |
220 else if (chr == "," && sep == "") | 409 if (chr == '"' || chr == "'") |
221 { | 410 sep = chr; |
222 result.push(prefix + selector.substring(start, j)); | 411 else if (chr == ",") |
223 start = j + 1; | 412 { |
| 413 result.push(prefix + selector.substring(start, j)); |
| 414 start = j + 1; |
| 415 } |
224 } | 416 } |
225 } | 417 } |
226 | 418 |
227 result.push(prefix + selector.substring(start)); | 419 result.push(prefix + selector.substring(start)); |
228 } | 420 } |
229 | 421 |
230 return result; | 422 return result; |
231 } | 423 } |
232 | 424 |
233 function init(document) | 425 function init(document) |
234 { | 426 { |
| 427 var shadow = null; |
| 428 var style = null; |
| 429 var observer = null; |
| 430 var tracer = null; |
| 431 var propertyFilters = new CSSPropertyFilters(window, addElemHideSelectors); |
| 432 |
235 // Use Shadow DOM if available to don't mess with web pages that rely on | 433 // Use Shadow DOM if available to don't mess with web pages that rely on |
236 // the order of their own <style> tags (#309). | 434 // the order of their own <style> tags (#309). |
237 // | 435 // |
238 // However, creating a shadow root breaks running CSS transitions. So we | 436 // However, creating a shadow root breaks running CSS transitions. So we |
239 // have to create the shadow root before transistions might start (#452). | 437 // have to create the shadow root before transistions might start (#452). |
240 // | 438 // |
241 // Also, we can't use shadow DOM on Google Docs, since it breaks printing | 439 // Also, using shadow DOM causes issues on some Google websites, |
242 // there (#1770). | 440 // including Google Docs and Gmail (#1770, #2602). |
243 var shadow = null; | 441 if ("createShadowRoot" in document.documentElement && !/\.google\.com$/.test(d
ocument.domain)) |
244 if ("createShadowRoot" in document.documentElement && document.domain != "docs
.google.com") | |
245 { | 442 { |
246 shadow = document.documentElement.createShadowRoot(); | 443 shadow = document.documentElement.createShadowRoot(); |
247 shadow.appendChild(document.createElement("shadow")); | 444 shadow.appendChild(document.createElement("shadow")); |
248 } | 445 } |
249 | 446 |
250 // Sets the currently used CSS rules for elemhide filters | 447 function addElemHideSelectors(selectors) |
251 var setElemhideCSSRules = function(response) | 448 { |
252 { | 449 if (selectors.length == 0) |
253 if (response.selectors.length == 0) | |
254 return; | 450 return; |
255 | 451 |
256 var style = document.createElement("style"); | 452 if (!style) |
257 style.setAttribute("type", "text/css"); | 453 { |
258 | 454 // Create <style> element lazily, only if we add styles. Add it to |
259 var selectors = response.selectors; | 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. |
260 if (shadow) | 474 if (shadow) |
261 { | |
262 shadow.appendChild(style); | |
263 selectors = convertSelectorsForShadowDOM(selectors); | 475 selectors = convertSelectorsForShadowDOM(selectors); |
264 } | 476 |
265 else | 477 // WebKit (and Blink?) apparently chokes when the selector list in a |
266 { | 478 // CSS rule is huge. So we split the elemhide selectors into groups. |
267 // Try to insert the style into the <head> tag, inserting directly under t
he | 479 while (selectors.length > 0) |
268 // document root breaks dev tools functionality: | 480 { |
269 // http://code.google.com/p/chromium/issues/detail?id=178109 | 481 var selector = selectors.splice(0, SELECTOR_GROUP_SIZE).join(", "); |
270 (document.head || document.documentElement).appendChild(style); | 482 style.sheet.addRule(selector, "display: none !important;"); |
271 } | 483 } |
272 | 484 }; |
273 var setRules = function() | 485 |
274 { | 486 var updateStylesheet = function() |
275 // The sheet property might not exist yet if the | 487 { |
276 // <style> element was created for a sub frame | 488 var selectors = null; |
277 if (!style.sheet) | 489 var CSSPropertyFiltersLoaded = false; |
278 { | 490 |
279 setTimeout(setRules, 0); | 491 var checkLoaded = function() |
| 492 { |
| 493 if (!selectors || !CSSPropertyFiltersLoaded) |
280 return; | 494 return; |
281 } | 495 |
282 | 496 if (observer) |
283 // WebKit apparently chokes when the selector list in a CSS rule is huge. | 497 observer.disconnect(); |
284 // So we split the elemhide selectors into groups. | 498 observer = null; |
285 for (var i = 0; selectors.length > 0; i++) | 499 |
286 { | 500 if (tracer) |
287 var selector = selectors.splice(0, SELECTOR_GROUP_SIZE).join(", "); | 501 tracer.disconnect(); |
288 style.sheet.insertRule(selector + " { display: none !important; }", i); | 502 tracer = null; |
289 } | 503 |
290 | 504 if (style && style.parentElement) |
291 if (response.trace) | 505 style.parentElement.removeChild(style); |
292 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); |
293 }; | 513 }; |
294 | 514 |
295 setRules(); | 515 ext.backgroundPage.sendMessage({type: "get-selectors"}, function(response) |
296 reinjectRulesWhenRemoved(document, style); | 516 { |
| 517 selectors = response; |
| 518 checkLoaded(); |
| 519 }); |
| 520 |
| 521 propertyFilters.load(function() |
| 522 { |
| 523 CSSPropertyFiltersLoaded = true; |
| 524 checkLoaded(); |
| 525 }); |
297 }; | 526 }; |
| 527 |
| 528 updateStylesheet(); |
298 | 529 |
299 document.addEventListener("error", function(event) | 530 document.addEventListener("error", function(event) |
300 { | 531 { |
301 checkCollapse(event.target); | 532 checkCollapse(event.target); |
302 }, true); | 533 }, true); |
303 | 534 |
304 document.addEventListener("load", function(event) | 535 document.addEventListener("load", function(event) |
305 { | 536 { |
306 var element = event.target; | 537 var element = event.target; |
307 | 538 |
308 if (/^i?frame$/.test(element.localName)) | 539 if (/^i?frame$/.test(element.localName)) |
309 checkCollapse(element); | 540 checkCollapse(element); |
310 | 541 |
311 // prior to Chrome 37, content scripts cannot run on about:blank, | 542 if (/\bChrome\//.test(navigator.userAgent)) |
312 // about:srcdoc and javascript: URLs. Moreover, as of Chrome 40 | 543 { |
313 // "load" and "error" events aren't dispatched there. So we have | 544 var contentDocument = getContentDocument(element); |
314 // to apply element hiding and collapsing from the parent frame. | 545 if (contentDocument) |
315 if (/\bChrome\//.test(navigator.userAgent) && isInlineFrame(element)) | 546 { |
316 { | 547 var contentWindow = contentDocument.defaultView; |
317 init(element.contentDocument); | 548 if (contentDocument instanceof contentWindow.HTMLDocument) |
318 | 549 { |
319 for (var tagName in typeMap) | 550 // Prior to Chrome 37, content scripts cannot run in |
320 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 } |
321 } | 568 } |
322 }, true); | 569 }, true); |
323 | 570 |
324 ext.backgroundPage.sendMessage({type: "get-selectors"}, setElemhideCSSRules); | 571 return updateStylesheet; |
325 } | 572 } |
326 | 573 |
327 if (document instanceof HTMLDocument) | 574 if (document instanceof HTMLDocument) |
328 { | 575 { |
329 checkSitekey(); | 576 checkSitekey(); |
330 init(document); | 577 window.updateStylesheet = init(document); |
331 } | 578 } |
LEFT | RIGHT |