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