OLD | NEW |
1 /* | 1 /* |
2 * This file is part of Adblock Plus <https://adblockplus.org/>, | 2 * This file is part of Adblock Plus <https://adblockplus.org/>, |
3 * Copyright (C) 2006-2015 Eyeo GmbH | 3 * Copyright (C) 2006-2015 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 |
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
97 if (!contentDocument) | 97 if (!contentDocument) |
98 return false; | 98 return false; |
99 | 99 |
100 // Return true, if the element is a first-party frame which doesn't | 100 // Return true, if the element is a first-party frame which doesn't |
101 // have this function, hence our content script isn't running there. | 101 // have this function, hence our content script isn't running there. |
102 // Those are dynamically created frames as well as frames | 102 // Those are dynamically created frames as well as frames |
103 // with "about:blank", "about:srcdoc" and "javascript:" URL. | 103 // with "about:blank", "about:srcdoc" and "javascript:" URL. |
104 return !("isFrameWithoutContentScript" in contentDocument.defaultView); | 104 return !("isFrameWithoutContentScript" in contentDocument.defaultView); |
105 } | 105 } |
106 | 106 |
| 107 function traceHiddenElements(document, selectors) |
| 108 { |
| 109 var changedNodes = []; |
| 110 var timeout = null; |
| 111 |
| 112 var checkNodes = function(nodes) |
| 113 { |
| 114 var matchedSelectors = []; |
| 115 |
| 116 // Find all selectors that match any hidden element inside the given nodes. |
| 117 for (var i = 0; i < selectors.length; i++) |
| 118 { |
| 119 var selector = selectors[i]; |
| 120 |
| 121 for (var j = 0; j < nodes.length; j++) |
| 122 { |
| 123 var elements = nodes[j].querySelectorAll(selector); |
| 124 var matched = false; |
| 125 |
| 126 for (var k = 0; k < elements.length; k++) |
| 127 { |
| 128 // Only consider selectors that actually have an effect on the |
| 129 // computed styles, and aren't overridden by rules with higher |
| 130 // priority, or haven't been circumvented in a different way. |
| 131 if (getComputedStyle(elements[k]).display == "none") |
| 132 { |
| 133 matchedSelectors.push(selector); |
| 134 matched = true; |
| 135 break; |
| 136 } |
| 137 } |
| 138 |
| 139 if (matched) |
| 140 break; |
| 141 } |
| 142 } |
| 143 |
| 144 if (matchedSelectors.length > 0) |
| 145 ext.backgroundPage.sendMessage({type: "trace-elemhide", selectors: matched
Selectors}); |
| 146 }; |
| 147 |
| 148 var observer = new MutationObserver(function(mutations) |
| 149 { |
| 150 // Forget previously changed nodes that are no longer in the DOM. |
| 151 for (var i = 0; i < changedNodes.length; i++) |
| 152 { |
| 153 if (!document.contains(changedNodes[i])) |
| 154 changedNodes.splice(i--, 1); |
| 155 } |
| 156 |
| 157 for (var j = 0; j < mutations.length; j++) |
| 158 { |
| 159 var mutation = mutations[j]; |
| 160 var node = mutation.target; |
| 161 |
| 162 // Ignore mutations of nodes that aren't in the DOM anymore. |
| 163 if (!document.contains(node)) |
| 164 continue; |
| 165 |
| 166 // Since querySelectorAll() doesn't consider the root itself |
| 167 // and since CSS selectors can also match siblings, we have |
| 168 // to consider the parent node for attribute mutations. |
| 169 if (mutation.type == "attributes") |
| 170 node = node.parentNode; |
| 171 |
| 172 var addNode = true; |
| 173 for (var k = 0; k < changedNodes.length; k++) |
| 174 { |
| 175 var previouslyChangedNode = changedNodes[k]; |
| 176 |
| 177 // If we are already going to check an ancestor of this node, |
| 178 // we can ignore this node, since it will be considered anyway |
| 179 // when checking one of its ancestors. |
| 180 if (previouslyChangedNode.contains(node)) |
| 181 { |
| 182 addNode = false; |
| 183 break; |
| 184 } |
| 185 |
| 186 // If this node is an ancestor of a node that previously changed, |
| 187 // we can ignore that node, since it will be considered anyway |
| 188 // when checking one of its ancestors. |
| 189 if (node.contains(previouslyChangedNode)) |
| 190 changedNodes.splice(k--, 1); |
| 191 } |
| 192 |
| 193 if (addNode) |
| 194 changedNodes.push(node); |
| 195 } |
| 196 |
| 197 // 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 |
| 199 // (like YouTube) freeze when the devtools panel is active. |
| 200 if (!timeout) |
| 201 { |
| 202 timeout = setTimeout(function() |
| 203 { |
| 204 checkNodes(changedNodes); |
| 205 changedNodes = []; |
| 206 timeout = null; |
| 207 }, 1000); |
| 208 } |
| 209 }); |
| 210 |
| 211 var startTracing = function() |
| 212 { |
| 213 checkNodes([document]); |
| 214 |
| 215 observer.observe( |
| 216 document, |
| 217 { |
| 218 childList: true, |
| 219 attributes: true, |
| 220 subtree: true |
| 221 } |
| 222 ); |
| 223 }; |
| 224 |
| 225 var stopTracing = function() |
| 226 { |
| 227 document.removeEventListener("DOMContentLoaded", startTracing); |
| 228 observer.disconnect(); |
| 229 clearTimeout(timeout); |
| 230 }; |
| 231 |
| 232 if (document.readyState == "loading") |
| 233 document.addEventListener("DOMContentLoaded", startTracing); |
| 234 else |
| 235 startTracing(); |
| 236 |
| 237 return stopTracing; |
| 238 } |
| 239 |
107 function reinjectRulesWhenRemoved(document, style) | 240 function reinjectRulesWhenRemoved(document, style) |
108 { | 241 { |
109 var MutationObserver = window.MutationObserver || window.WebKitMutationObserve
r; | 242 var MutationObserver = window.MutationObserver || window.WebKitMutationObserve
r; |
110 if (!MutationObserver) | 243 if (!MutationObserver) |
111 return; | 244 return; |
112 | 245 |
113 var observer = new MutationObserver(function(mutations) | 246 var observer = new MutationObserver(function(mutations) |
114 { | 247 { |
115 var isStyleRemoved = false; | 248 var isStyleRemoved = false; |
116 for (var i = 0; i < mutations.length; i++) | 249 for (var i = 0; i < mutations.length; i++) |
(...skipping 10 matching lines...) Expand all Loading... |
127 observer.disconnect(); | 260 observer.disconnect(); |
128 | 261 |
129 var n = document.styleSheets.length; | 262 var n = document.styleSheets.length; |
130 if (n == 0) | 263 if (n == 0) |
131 return; | 264 return; |
132 | 265 |
133 var stylesheet = document.styleSheets[n - 1]; | 266 var stylesheet = document.styleSheets[n - 1]; |
134 ext.backgroundPage.sendMessage( | 267 ext.backgroundPage.sendMessage( |
135 {type: "get-selectors"}, | 268 {type: "get-selectors"}, |
136 | 269 |
137 function(selectors) | 270 function(response) |
138 { | 271 { |
| 272 var selectors = response.selectors; |
139 while (selectors.length > 0) | 273 while (selectors.length > 0) |
140 { | 274 { |
141 var selector = selectors.splice(0, SELECTOR_GROUP_SIZE).join(", "); | 275 var selector = selectors.splice(0, SELECTOR_GROUP_SIZE).join(", "); |
142 | 276 |
143 // Using non-standard addRule() here. This is the only way | 277 // Using non-standard addRule() here. This is the only way |
144 // to add rules at the end of a cross-origin stylesheet | 278 // to add rules at the end of a cross-origin stylesheet |
145 // because we don't know how many rules are already in there | 279 // because we don't know how many rules are already in there |
146 stylesheet.addRule(selector, "display: none !important;"); | 280 stylesheet.addRule(selector, "display: none !important;"); |
147 } | 281 } |
148 } | 282 } |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
183 result.push(prefix + selector.substring(start)); | 317 result.push(prefix + selector.substring(start)); |
184 } | 318 } |
185 | 319 |
186 return result; | 320 return result; |
187 } | 321 } |
188 | 322 |
189 function init(document) | 323 function init(document) |
190 { | 324 { |
191 var shadow = null; | 325 var shadow = null; |
192 var style = null; | 326 var style = null; |
193 var observer = null; | 327 |
| 328 var reinjectObserver = null; |
| 329 var stopTracing = null; |
194 | 330 |
195 // Use Shadow DOM if available to don't mess with web pages that rely on | 331 // Use Shadow DOM if available to don't mess with web pages that rely on |
196 // the order of their own <style> tags (#309). | 332 // the order of their own <style> tags (#309). |
197 // | 333 // |
198 // However, creating a shadow root breaks running CSS transitions. So we | 334 // However, creating a shadow root breaks running CSS transitions. So we |
199 // have to create the shadow root before transistions might start (#452). | 335 // have to create the shadow root before transistions might start (#452). |
200 // | 336 // |
201 // Also, we can't use shadow DOM on Google Docs, since it breaks printing | 337 // Also, we can't use shadow DOM on Google Docs, since it breaks printing |
202 // there (#1770). | 338 // there (#1770). |
203 if ("createShadowRoot" in document.documentElement && document.domain != "docs
.google.com") | 339 if ("createShadowRoot" in document.documentElement && document.domain != "docs
.google.com") |
204 { | 340 { |
205 shadow = document.documentElement.createShadowRoot(); | 341 shadow = document.documentElement.createShadowRoot(); |
206 shadow.appendChild(document.createElement("shadow")); | 342 shadow.appendChild(document.createElement("shadow")); |
207 } | 343 } |
208 | 344 |
209 var updateStylesheet = function(reinject) | 345 var updateStylesheet = function(reinject) |
210 { | 346 { |
211 ext.backgroundPage.sendMessage({type: "get-selectors"}, function(selectors) | 347 ext.backgroundPage.sendMessage({type: "get-selectors"}, function(response) |
212 { | 348 { |
213 if (observer) | 349 if (reinjectObserver) |
214 { | 350 { |
215 observer.disconnect(); | 351 reinjectObserver.disconnect(); |
216 observer = null; | 352 reinjectObserver = null; |
| 353 } |
| 354 |
| 355 if (stopTracing) |
| 356 { |
| 357 stopTracing(); |
| 358 stopTracing = null; |
217 } | 359 } |
218 | 360 |
219 if (style && style.parentElement) | 361 if (style && style.parentElement) |
220 { | 362 { |
221 style.parentElement.removeChild(style); | 363 style.parentElement.removeChild(style); |
222 style = null; | 364 style = null; |
223 } | 365 } |
224 | 366 |
| 367 var selectors = response.selectors; |
225 if (selectors.length > 0) | 368 if (selectors.length > 0) |
226 { | 369 { |
227 // Create <style> element lazily, only if we add styles. Add it to | 370 // Create <style> element lazily, only if we add styles. Add it to |
228 // the shadow DOM if possible. Otherwise fallback to the <head> or | 371 // the shadow DOM if possible. Otherwise fallback to the <head> or |
229 // <html> element. If we have injected a style element before that | 372 // <html> element. If we have injected a style element before that |
230 // has been removed (the sheet property is null), create a new one. | 373 // has been removed (the sheet property is null), create a new one. |
231 style = document.createElement("style"); | 374 style = document.createElement("style"); |
232 (shadow || document.head || document.documentElement).appendChild(style)
; | 375 (shadow || document.head || document.documentElement).appendChild(style)
; |
233 | 376 |
234 // It can happen that the frame already navigated to a different | 377 // It can happen that the frame already navigated to a different |
235 // document while we were waiting for the background page to respond. | 378 // document while we were waiting for the background page to respond. |
236 // In that case the sheet property will stay null, after addind the | 379 // In that case the sheet property will stay null, after addind the |
237 // <style> element to the shadow DOM. | 380 // <style> element to the shadow DOM. |
238 if (style.sheet) | 381 if (style.sheet) |
239 { | 382 { |
240 // If using shadow DOM, we have to add the ::content pseudo-element | 383 // If using shadow DOM, we have to add the ::content pseudo-element |
241 // before each selector, in order to match elements within the | 384 // before each selector, in order to match elements within the |
242 // insertion point. | 385 // insertion point. |
243 if (shadow) | 386 if (shadow) |
244 selectors = convertSelectorsForShadowDOM(selectors); | 387 selectors = convertSelectorsForShadowDOM(selectors); |
245 | 388 |
246 // WebKit (and Blink?) apparently chokes when the selector list in a | 389 // WebKit (and Blink?) apparently chokes when the selector list in a |
247 // CSS rule is huge. So we split the elemhide selectors into groups. | 390 // CSS rule is huge. So we split the elemhide selectors into groups. |
248 for (var i = 0; selectors.length > 0; i++) | 391 for (var i = 0; selectors.length > 0; i++) |
249 { | 392 { |
250 var selector = selectors.splice(0, SELECTOR_GROUP_SIZE).join(", "); | 393 var selector = selectors.splice(0, SELECTOR_GROUP_SIZE).join(", "); |
251 style.sheet.insertRule(selector + " { display: none !important; }",
i); | 394 style.sheet.insertRule(selector + " { display: none !important; }",
i); |
252 } | 395 } |
| 396 |
| 397 reinjectObserver = reinjectRulesWhenRemoved(document, style); |
| 398 |
| 399 if (response.trace) |
| 400 stopTracing = traceHiddenElements(document, response.selectors); |
253 } | 401 } |
254 | |
255 observer = reinjectRulesWhenRemoved(document, style); | |
256 } | 402 } |
257 }); | 403 }); |
258 }; | 404 }; |
259 | 405 |
260 updateStylesheet(); | 406 updateStylesheet(); |
261 | 407 |
262 document.addEventListener("error", function(event) | 408 document.addEventListener("error", function(event) |
263 { | 409 { |
264 checkCollapse(event.target); | 410 checkCollapse(event.target); |
265 }, true); | 411 }, true); |
(...skipping 19 matching lines...) Expand all Loading... |
285 }, true); | 431 }, true); |
286 | 432 |
287 return updateStylesheet; | 433 return updateStylesheet; |
288 } | 434 } |
289 | 435 |
290 if (document instanceof HTMLDocument) | 436 if (document instanceof HTMLDocument) |
291 { | 437 { |
292 checkSitekey(); | 438 checkSitekey(); |
293 window.updateStylesheet = init(document); | 439 window.updateStylesheet = init(document); |
294 } | 440 } |
OLD | NEW |