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