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-2016 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 |
(...skipping 178 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
189 try | 189 try |
190 { | 190 { |
191 return element.contentDocument; | 191 return element.contentDocument; |
192 } | 192 } |
193 catch (e) | 193 catch (e) |
194 { | 194 { |
195 return null; | 195 return null; |
196 } | 196 } |
197 } | 197 } |
198 | 198 |
199 function ElementHidingTracer(document, selectors) | 199 function ElementHidingTracer(selectors) |
200 { | 200 { |
201 this.document = document; | |
202 this.selectors = selectors; | 201 this.selectors = selectors; |
203 | 202 |
204 this.changedNodes = []; | 203 this.changedNodes = []; |
205 this.timeout = null; | 204 this.timeout = null; |
206 | 205 |
207 this.observer = new MutationObserver(this.observe.bind(this)); | 206 this.observer = new MutationObserver(this.observe.bind(this)); |
208 this.trace = this.trace.bind(this); | 207 this.trace = this.trace.bind(this); |
209 | 208 |
210 if (document.readyState == "loading") | 209 if (document.readyState == "loading") |
211 document.addEventListener("DOMContentLoaded", this.trace); | 210 document.addEventListener("DOMContentLoaded", this.trace); |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
257 this.checkNodes(this.changedNodes); | 256 this.checkNodes(this.changedNodes); |
258 this.changedNodes = []; | 257 this.changedNodes = []; |
259 this.timeout = null; | 258 this.timeout = null; |
260 }, | 259 }, |
261 | 260 |
262 observe: function(mutations) | 261 observe: function(mutations) |
263 { | 262 { |
264 // Forget previously changed nodes that are no longer in the DOM. | 263 // Forget previously changed nodes that are no longer in the DOM. |
265 for (var i = 0; i < this.changedNodes.length; i++) | 264 for (var i = 0; i < this.changedNodes.length; i++) |
266 { | 265 { |
267 if (!this.document.contains(this.changedNodes[i])) | 266 if (!document.contains(this.changedNodes[i])) |
268 this.changedNodes.splice(i--, 1); | 267 this.changedNodes.splice(i--, 1); |
269 } | 268 } |
270 | 269 |
271 for (var j = 0; j < mutations.length; j++) | 270 for (var j = 0; j < mutations.length; j++) |
272 { | 271 { |
273 var mutation = mutations[j]; | 272 var mutation = mutations[j]; |
274 var node = mutation.target; | 273 var node = mutation.target; |
275 | 274 |
276 // Ignore mutations of nodes that aren't in the DOM anymore. | 275 // Ignore mutations of nodes that aren't in the DOM anymore. |
277 if (!this.document.contains(node)) | 276 if (!document.contains(node)) |
278 continue; | 277 continue; |
279 | 278 |
280 // Since querySelectorAll() doesn't consider the root itself | 279 // Since querySelectorAll() doesn't consider the root itself |
281 // and since CSS selectors can also match siblings, we have | 280 // and since CSS selectors can also match siblings, we have |
282 // to consider the parent node for attribute mutations. | 281 // to consider the parent node for attribute mutations. |
283 if (mutation.type == "attributes") | 282 if (mutation.type == "attributes") |
284 node = node.parentNode; | 283 node = node.parentNode; |
285 | 284 |
286 var addNode = true; | 285 var addNode = true; |
287 for (var k = 0; k < this.changedNodes.length; k++) | 286 for (var k = 0; k < this.changedNodes.length; k++) |
(...skipping 22 matching lines...) Expand all Loading... |
310 | 309 |
311 // Check only nodes whose descendants have changed, and not more often | 310 // Check only nodes whose descendants have changed, and not more often |
312 // than once a second. Otherwise large pages with a lot of DOM mutations | 311 // than once a second. Otherwise large pages with a lot of DOM mutations |
313 // (like YouTube) freeze when the devtools panel is active. | 312 // (like YouTube) freeze when the devtools panel is active. |
314 if (this.timeout == null) | 313 if (this.timeout == null) |
315 this.timeout = setTimeout(this.onTimeout.bind(this), 1000); | 314 this.timeout = setTimeout(this.onTimeout.bind(this), 1000); |
316 }, | 315 }, |
317 | 316 |
318 trace: function() | 317 trace: function() |
319 { | 318 { |
320 this.checkNodes([this.document]); | 319 this.checkNodes([document]); |
321 | 320 |
322 this.observer.observe( | 321 this.observer.observe( |
323 this.document, | 322 document, |
324 { | 323 { |
325 childList: true, | 324 childList: true, |
326 attributes: true, | 325 attributes: true, |
327 subtree: true | 326 subtree: true |
328 } | 327 } |
329 ); | 328 ); |
330 }, | 329 }, |
331 | 330 |
332 disconnect: function() | 331 disconnect: function() |
333 { | 332 { |
334 this.document.removeEventListener("DOMContentLoaded", this.trace); | 333 document.removeEventListener("DOMContentLoaded", this.trace); |
335 this.observer.disconnect(); | 334 this.observer.disconnect(); |
336 clearTimeout(this.timeout); | 335 clearTimeout(this.timeout); |
337 } | 336 } |
338 }; | 337 }; |
339 | 338 |
340 function runInDocument(document, fn, arg) | 339 function runInDocument(fn, arg) |
341 { | 340 { |
342 var script = document.createElement("script"); | 341 var script = document.createElement("script"); |
343 script.type = "application/javascript"; | 342 script.type = "application/javascript"; |
344 script.async = false; | 343 script.async = false; |
345 script.textContent = "(" + fn + ")(" + JSON.stringify(arg) + ");"; | 344 script.textContent = "(" + fn + ")(" + JSON.stringify(arg) + ");"; |
346 document.documentElement.appendChild(script); | 345 document.documentElement.appendChild(script); |
347 document.documentElement.removeChild(script); | 346 document.documentElement.removeChild(script); |
348 } | 347 } |
349 | 348 |
350 // Neither Chrome[1] nor Safari allow us to intercept WebSockets, and therefore | 349 // Neither Chrome[1] nor Safari allow us to intercept WebSockets, and therefore |
351 // some ad networks are misusing them as a way to serve adverts and circumvent | 350 // some ad networks are misusing them as a way to serve adverts and circumvent |
352 // us. As a workaround we wrap WebSocket, preventing blocked WebSocket | 351 // us. As a workaround we wrap WebSocket, preventing blocked WebSocket |
353 // connections from being opened. | 352 // connections from being opened. |
354 // [1] - https://bugs.chromium.org/p/chromium/issues/detail?id=129353 | 353 // [1] - https://bugs.chromium.org/p/chromium/issues/detail?id=129353 |
355 function wrapWebSocket(document) | 354 function wrapWebSocket() |
356 { | 355 { |
357 if (typeof WebSocket == "undefined") | 356 if (typeof WebSocket == "undefined") |
358 return; | 357 return; |
359 | 358 |
360 var eventName = "abpws-" + Math.random().toString(36).substr(2); | 359 var eventName = "abpws-" + Math.random().toString(36).substr(2); |
361 | 360 |
362 document.addEventListener(eventName, function(event) | 361 document.addEventListener(eventName, function(event) |
363 { | 362 { |
364 ext.backgroundPage.sendMessage({ | 363 ext.backgroundPage.sendMessage({ |
365 type: "request.websocket", | 364 type: "request.websocket", |
366 url: event.detail.url | 365 url: event.detail.url |
367 }, function (block) | 366 }, function (block) |
368 { | 367 { |
369 document.dispatchEvent( | 368 document.dispatchEvent( |
370 new CustomEvent(eventName + "-" + event.detail.url, {detail: block}) | 369 new CustomEvent(eventName + "-" + event.detail.url, {detail: block}) |
371 ); | 370 ); |
372 }); | 371 }); |
373 }); | 372 }); |
374 | 373 |
375 runInDocument(document, function(eventName) | 374 runInDocument(function(eventName) |
376 { | 375 { |
377 // As far as possible we must track everything we use that could be | 376 // As far as possible we must track everything we use that could be |
378 // sabotaged by the website later in order to circumvent us. | 377 // sabotaged by the website later in order to circumvent us. |
379 var RealWebSocket = WebSocket; | 378 var RealWebSocket = WebSocket; |
380 var closeWebSocket = Function.prototype.call.bind(RealWebSocket.prototype.cl
ose); | 379 var closeWebSocket = Function.prototype.call.bind(RealWebSocket.prototype.cl
ose); |
381 var addEventListener = document.addEventListener.bind(document); | 380 var addEventListener = document.addEventListener.bind(document); |
382 var removeEventListener = document.removeEventListener.bind(document); | 381 var removeEventListener = document.removeEventListener.bind(document); |
383 var dispatchEvent = document.dispatchEvent.bind(document); | 382 var dispatchEvent = document.dispatchEvent.bind(document); |
384 var CustomEvent = window.CustomEvent; | 383 var CustomEvent = window.CustomEvent; |
385 | 384 |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
425 OPEN: {value: RealWebSocket.OPEN, enumerable: true}, | 424 OPEN: {value: RealWebSocket.OPEN, enumerable: true}, |
426 CLOSING: {value: RealWebSocket.CLOSING, enumerable: true}, | 425 CLOSING: {value: RealWebSocket.CLOSING, enumerable: true}, |
427 CLOSED: {value: RealWebSocket.CLOSED, enumerable: true}, | 426 CLOSED: {value: RealWebSocket.CLOSED, enumerable: true}, |
428 prototype: {value: RealWebSocket.prototype} | 427 prototype: {value: RealWebSocket.prototype} |
429 }); | 428 }); |
430 | 429 |
431 RealWebSocket.prototype.constructor = WebSocket; | 430 RealWebSocket.prototype.constructor = WebSocket; |
432 }, eventName); | 431 }, eventName); |
433 } | 432 } |
434 | 433 |
435 function init(document) | 434 function init() |
436 { | 435 { |
437 var shadow = null; | 436 var shadow = null; |
438 var style = null; | 437 var style = null; |
439 var observer = null; | 438 var observer = null; |
440 var tracer = null; | 439 var tracer = null; |
441 | 440 |
442 wrapWebSocket(document); | 441 wrapWebSocket(); |
443 | 442 |
444 function getPropertyFilters(callback) | 443 function getPropertyFilters(callback) |
445 { | 444 { |
446 ext.backgroundPage.sendMessage({ | 445 ext.backgroundPage.sendMessage({ |
447 type: "filters.get", | 446 type: "filters.get", |
448 what: "cssproperties" | 447 what: "cssproperties" |
449 }, callback); | 448 }, callback); |
450 } | 449 } |
451 var propertyFilters = new CSSPropertyFilters(window, getPropertyFilters, | 450 var propertyFilters = new CSSPropertyFilters(window, getPropertyFilters, |
452 addElemHideSelectors); | 451 addElemHideSelectors); |
453 | 452 |
454 // Use Shadow DOM if available to don't mess with web pages that rely on | 453 // Use Shadow DOM if available to don't mess with web pages that rely on |
455 // the order of their own <style> tags (#309). | 454 // the order of their own <style> tags (#309). |
456 // | 455 // |
457 // However, creating a shadow root breaks running CSS transitions. So we | 456 // However, creating a shadow root breaks running CSS transitions. So we |
458 // have to create the shadow root before transistions might start (#452). | 457 // have to create the shadow root before transistions might start (#452). |
459 // | 458 // |
460 // Also, using shadow DOM causes issues on some Google websites, | 459 // Also, using shadow DOM causes issues on some Google websites, |
461 // including Google Docs, Gmail and Blogger (#1770, #2602, #2687). | 460 // including Google Docs, Gmail and Blogger (#1770, #2602, #2687). |
462 if ("createShadowRoot" in document.documentElement && | 461 if ("createShadowRoot" in document.documentElement && |
463 !/\.(?:google|blogger)\.com$/.test(document.domain)) | 462 !/\.(?:google|blogger)\.com$/.test(document.domain)) |
464 { | 463 { |
465 shadow = document.documentElement.createShadowRoot(); | 464 shadow = document.documentElement.createShadowRoot(); |
466 shadow.appendChild(document.createElement("shadow")); | 465 shadow.appendChild(document.createElement("shadow")); |
467 | 466 |
468 // Stop the website from messing with our shadowRoot | 467 // Stop the website from messing with our shadowRoot |
469 if ("shadowRoot" in Element.prototype) | 468 if ("shadowRoot" in Element.prototype) |
470 { | 469 { |
471 runInDocument(document, function() | 470 runInDocument(function() |
472 { | 471 { |
473 var ourShadowRoot = document.documentElement.shadowRoot; | 472 var ourShadowRoot = document.documentElement.shadowRoot; |
474 var desc = Object.getOwnPropertyDescriptor(Element.prototype, "shadowRoo
t"); | 473 var desc = Object.getOwnPropertyDescriptor(Element.prototype, "shadowRoo
t"); |
475 var shadowRoot = Function.prototype.call.bind(desc.get); | 474 var shadowRoot = Function.prototype.call.bind(desc.get); |
476 | 475 |
477 Object.defineProperty(Element.prototype, "shadowRoot", { | 476 Object.defineProperty(Element.prototype, "shadowRoot", { |
478 configurable: true, enumerable: true, get: function() | 477 configurable: true, enumerable: true, get: function() |
479 { | 478 { |
480 var shadow = shadowRoot(this); | 479 var shadow = shadowRoot(this); |
481 return shadow == ourShadowRoot ? null : shadow; | 480 return shadow == ourShadowRoot ? null : shadow; |
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
553 tracer = null; | 552 tracer = null; |
554 | 553 |
555 if (style && style.parentElement) | 554 if (style && style.parentElement) |
556 style.parentElement.removeChild(style); | 555 style.parentElement.removeChild(style); |
557 style = null; | 556 style = null; |
558 | 557 |
559 addElemHideSelectors(selectors.selectors); | 558 addElemHideSelectors(selectors.selectors); |
560 propertyFilters.apply(); | 559 propertyFilters.apply(); |
561 | 560 |
562 if (selectors.trace) | 561 if (selectors.trace) |
563 tracer = new ElementHidingTracer(document, selectors.selectors); | 562 tracer = new ElementHidingTracer(selectors.selectors); |
564 }; | 563 }; |
565 | 564 |
566 ext.backgroundPage.sendMessage({type: "get-selectors"}, function(response) | 565 ext.backgroundPage.sendMessage({type: "get-selectors"}, function(response) |
567 { | 566 { |
568 selectors = response; | 567 selectors = response; |
569 checkLoaded(); | 568 checkLoaded(); |
570 }); | 569 }); |
571 | 570 |
572 propertyFilters.load(function() | 571 propertyFilters.load(function() |
573 { | 572 { |
574 CSSPropertyFiltersLoaded = true; | 573 CSSPropertyFiltersLoaded = true; |
575 checkLoaded(); | 574 checkLoaded(); |
576 }); | 575 }); |
577 }; | 576 }; |
578 | 577 |
579 updateStylesheet(); | 578 updateStylesheet(); |
580 | 579 |
581 document.addEventListener("error", function(event) | 580 document.addEventListener("error", function(event) |
582 { | 581 { |
583 checkCollapse(event.target); | 582 checkCollapse(event.target); |
584 }, true); | 583 }, true); |
585 | 584 |
586 document.addEventListener("load", function(event) | 585 document.addEventListener("load", function(event) |
587 { | 586 { |
588 var element = event.target; | 587 var element = event.target; |
589 | |
590 if (/^i?frame$/.test(element.localName)) | 588 if (/^i?frame$/.test(element.localName)) |
591 checkCollapse(element); | 589 checkCollapse(element); |
592 | |
593 if (/\bChrome\//.test(navigator.userAgent)) | |
594 { | |
595 var contentDocument = getContentDocument(element); | |
596 if (contentDocument) | |
597 { | |
598 var contentWindow = contentDocument.defaultView; | |
599 if (contentDocument instanceof contentWindow.HTMLDocument) | |
600 { | |
601 // Prior to Chrome 37, content scripts cannot run in | |
602 // dynamically created frames. Also on Chrome 37-40 | |
603 // document_start content scripts (like this one) don't | |
604 // run either in those frames due to https://crbug.com/416907. | |
605 // So we have to apply element hiding from the parent frame. | |
606 if (!("init" in contentWindow)) | |
607 init(contentDocument); | |
608 } | |
609 } | |
610 } | |
611 }, true); | 590 }, true); |
612 | 591 |
613 return updateStylesheet; | 592 return updateStylesheet; |
614 } | 593 } |
615 | 594 |
616 if (document instanceof HTMLDocument) | 595 if (document instanceof HTMLDocument) |
617 { | 596 { |
618 checkSitekey(); | 597 checkSitekey(); |
619 window.updateStylesheet = init(document); | 598 window.updateStylesheet = init(); |
620 } | 599 } |
OLD | NEW |