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-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 |
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 MutationObserver = window.MutationObserver || window.WebKitMutationObserver; | 18 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver; |
19 var SELECTOR_GROUP_SIZE = 200; | |
20 | 19 |
21 var typeMap = { | 20 var typeMap = { |
22 "img": "IMAGE", | 21 "img": "IMAGE", |
23 "input": "IMAGE", | 22 "input": "IMAGE", |
24 "picture": "IMAGE", | 23 "picture": "IMAGE", |
25 "audio": "MEDIA", | 24 "audio": "MEDIA", |
26 "video": "MEDIA", | 25 "video": "MEDIA", |
27 "frame": "SUBDOCUMENT", | 26 "frame": "SUBDOCUMENT", |
28 "iframe": "SUBDOCUMENT", | 27 "iframe": "SUBDOCUMENT", |
29 "object": "OBJECT", | 28 "object": "OBJECT", |
(...skipping 299 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
329 }, | 328 }, |
330 | 329 |
331 disconnect: function() | 330 disconnect: function() |
332 { | 331 { |
333 document.removeEventListener("DOMContentLoaded", this.trace); | 332 document.removeEventListener("DOMContentLoaded", this.trace); |
334 this.observer.disconnect(); | 333 this.observer.disconnect(); |
335 clearTimeout(this.timeout); | 334 clearTimeout(this.timeout); |
336 } | 335 } |
337 }; | 336 }; |
338 | 337 |
339 function runInDocument(fn, arg) | 338 function runInPageContext(fn, arg) |
340 { | 339 { |
341 var script = document.createElement("script"); | 340 var script = document.createElement("script"); |
342 script.type = "application/javascript"; | 341 script.type = "application/javascript"; |
343 script.async = false; | 342 script.async = false; |
344 script.textContent = "(" + fn + ")(" + JSON.stringify(arg) + ");"; | 343 script.textContent = "(" + fn + ")(" + JSON.stringify(arg) + ");"; |
345 document.documentElement.appendChild(script); | 344 document.documentElement.appendChild(script); |
346 document.documentElement.removeChild(script); | 345 document.documentElement.removeChild(script); |
347 } | 346 } |
348 | 347 |
349 // Neither Chrome[1] nor Safari allow us to intercept WebSockets, and therefore | 348 // Neither Chrome[1] nor Safari allow us to intercept WebSockets, and therefore |
(...skipping 14 matching lines...) Expand all Loading... |
364 type: "request.websocket", | 363 type: "request.websocket", |
365 url: event.detail.url | 364 url: event.detail.url |
366 }, function (block) | 365 }, function (block) |
367 { | 366 { |
368 document.dispatchEvent( | 367 document.dispatchEvent( |
369 new CustomEvent(eventName + "-" + event.detail.url, {detail: block}) | 368 new CustomEvent(eventName + "-" + event.detail.url, {detail: block}) |
370 ); | 369 ); |
371 }); | 370 }); |
372 }); | 371 }); |
373 | 372 |
374 runInDocument(function(eventName) | 373 runInPageContext(function(eventName) |
375 { | 374 { |
376 // As far as possible we must track everything we use that could be | 375 // As far as possible we must track everything we use that could be |
377 // sabotaged by the website later in order to circumvent us. | 376 // sabotaged by the website later in order to circumvent us. |
378 var RealWebSocket = WebSocket; | 377 var RealWebSocket = WebSocket; |
379 var closeWebSocket = Function.prototype.call.bind(RealWebSocket.prototype.cl
ose); | 378 var closeWebSocket = Function.prototype.call.bind(RealWebSocket.prototype.cl
ose); |
380 var addEventListener = document.addEventListener.bind(document); | 379 var addEventListener = document.addEventListener.bind(document); |
381 var removeEventListener = document.removeEventListener.bind(document); | 380 var removeEventListener = document.removeEventListener.bind(document); |
382 var dispatchEvent = document.dispatchEvent.bind(document); | 381 var dispatchEvent = document.dispatchEvent.bind(document); |
383 var CustomEvent = window.CustomEvent; | 382 var CustomEvent = window.CustomEvent; |
384 | 383 |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
424 OPEN: {value: RealWebSocket.OPEN, enumerable: true}, | 423 OPEN: {value: RealWebSocket.OPEN, enumerable: true}, |
425 CLOSING: {value: RealWebSocket.CLOSING, enumerable: true}, | 424 CLOSING: {value: RealWebSocket.CLOSING, enumerable: true}, |
426 CLOSED: {value: RealWebSocket.CLOSED, enumerable: true}, | 425 CLOSED: {value: RealWebSocket.CLOSED, enumerable: true}, |
427 prototype: {value: RealWebSocket.prototype} | 426 prototype: {value: RealWebSocket.prototype} |
428 }); | 427 }); |
429 | 428 |
430 RealWebSocket.prototype.constructor = WebSocket; | 429 RealWebSocket.prototype.constructor = WebSocket; |
431 }, eventName); | 430 }, eventName); |
432 } | 431 } |
433 | 432 |
434 function init() | 433 function ElemHide() |
435 { | 434 { |
436 var shadow = null; | 435 this.shadow = this.createShadowTree(); |
437 var style = null; | 436 this.style = null; |
438 var observer = null; | 437 this.tracer = null; |
439 var tracer = null; | 438 |
440 | 439 this.propertyFilters = new CSSPropertyFilters( |
441 wrapWebSocket(); | 440 window, |
442 | 441 function(callback) |
443 function getPropertyFilters(callback) | 442 { |
444 { | 443 ext.backgroundPage.sendMessage({ |
445 ext.backgroundPage.sendMessage({ | 444 type: "filters.get", |
446 type: "filters.get", | 445 what: "cssproperties" |
447 what: "cssproperties" | 446 }, callback); |
448 }, callback); | 447 }, |
449 } | 448 this.addSelectors.bind(this) |
450 var propertyFilters = new CSSPropertyFilters(window, getPropertyFilters, | 449 ); |
451 addElemHideSelectors); | 450 } |
452 | 451 ElemHide.prototype = { |
453 // Use Shadow DOM if available to don't mess with web pages that rely on | 452 selectorGroupSize: 200, |
454 // the order of their own <style> tags (#309). | 453 |
455 // | 454 createShadowTree: function() |
456 // However, creating a shadow root breaks running CSS transitions. So we | 455 { |
457 // have to create the shadow root before transistions might start (#452). | 456 // Use Shadow DOM if available as to not mess with with web pages that |
458 // | 457 // rely on the order of their own <style> tags (#309). However, creating |
459 // Also, using shadow DOM causes issues on some Google websites, | 458 // a shadow root breaks running CSS transitions. So we have to create |
460 // including Google Docs, Gmail and Blogger (#1770, #2602, #2687). | 459 // the shadow root before transistions might start (#452). |
461 if ("createShadowRoot" in document.documentElement && | 460 if (!("createShadowRoot" in document.documentElement)) |
462 !/\.(?:google|blogger)\.com$/.test(document.domain)) | 461 return null; |
463 { | 462 |
464 shadow = document.documentElement.createShadowRoot(); | 463 // Using shadow DOM causes issues on some Google websites, |
| 464 // including Google Docs, Gmail and Blogger (#1770, #2602, #2687). |
| 465 if (/\.(?:google|blogger)\.com$/.test(document.domain)) |
| 466 return null; |
| 467 |
| 468 var shadow = document.documentElement.createShadowRoot(); |
465 shadow.appendChild(document.createElement("shadow")); | 469 shadow.appendChild(document.createElement("shadow")); |
466 | 470 |
467 // Stop the website from messing with our shadowRoot | 471 // Stop the website from messing with our shadow root (#4191, #4298). |
468 if ("shadowRoot" in Element.prototype) | 472 if ("shadowRoot" in Element.prototype) |
469 { | 473 { |
470 runInDocument(function() | 474 runInPageContext(function() |
471 { | 475 { |
472 var ourShadowRoot = document.documentElement.shadowRoot; | 476 var ourShadowRoot = document.documentElement.shadowRoot; |
473 var desc = Object.getOwnPropertyDescriptor(Element.prototype, "shadowRoo
t"); | 477 var desc = Object.getOwnPropertyDescriptor(Element.prototype, "shadowRoo
t"); |
474 var shadowRoot = Function.prototype.call.bind(desc.get); | 478 var shadowRoot = Function.prototype.call.bind(desc.get); |
475 | 479 |
476 Object.defineProperty(Element.prototype, "shadowRoot", { | 480 Object.defineProperty(Element.prototype, "shadowRoot", { |
477 configurable: true, enumerable: true, get: function() | 481 configurable: true, enumerable: true, get: function() |
478 { | 482 { |
479 var shadow = shadowRoot(this); | 483 var shadow = shadowRoot(this); |
480 return shadow == ourShadowRoot ? null : shadow; | 484 return shadow == ourShadowRoot ? null : shadow; |
481 } | 485 } |
482 }); | 486 }); |
483 }, null); | 487 }, null); |
484 } | 488 } |
485 } | 489 |
486 | 490 return shadow; |
487 function addElemHideSelectors(selectors) | 491 }, |
| 492 |
| 493 addSelectors: function(selectors) |
488 { | 494 { |
489 if (selectors.length == 0) | 495 if (selectors.length == 0) |
490 return; | 496 return; |
491 | 497 |
492 if (!style) | 498 if (!this.style) |
493 { | 499 { |
494 // Create <style> element lazily, only if we add styles. Add it to | 500 // Create <style> element lazily, only if we add styles. Add it to |
495 // the shadow DOM if possible. Otherwise fallback to the <head> or | 501 // the shadow DOM if possible. Otherwise fallback to the <head> or |
496 // <html> element. If we have injected a style element before that | 502 // <html> element. If we have injected a style element before that |
497 // has been removed (the sheet property is null), create a new one. | 503 // has been removed (the sheet property is null), create a new one. |
498 style = document.createElement("style"); | 504 this.style = document.createElement("style"); |
499 (shadow || document.head || document.documentElement).appendChild(style); | 505 (this.shadow || document.head |
| 506 || document.documentElement).appendChild(this.style); |
500 | 507 |
501 // It can happen that the frame already navigated to a different | 508 // It can happen that the frame already navigated to a different |
502 // document while we were waiting for the background page to respond. | 509 // document while we were waiting for the background page to respond. |
503 // In that case the sheet property will stay null, after addind the | 510 // In that case the sheet property will stay null, after addind the |
504 // <style> element to the shadow DOM. | 511 // <style> element to the shadow DOM. |
505 if (!style.sheet) | 512 if (!this.style.sheet) |
506 return; | 513 return; |
507 } | 514 } |
508 | 515 |
509 // If using shadow DOM, we have to add the ::content pseudo-element | 516 // If using shadow DOM, we have to add the ::content pseudo-element |
510 // before each selector, in order to match elements within the | 517 // before each selector, in order to match elements within the |
511 // insertion point. | 518 // insertion point. |
512 if (shadow) | 519 if (this.shadow) |
513 { | 520 { |
514 var preparedSelectors = []; | 521 var preparedSelectors = []; |
515 for (var i = 0; i < selectors.length; i++) | 522 for (var i = 0; i < selectors.length; i++) |
516 { | 523 { |
517 var subSelectors = splitSelector(selectors[i]); | 524 var subSelectors = splitSelector(selectors[i]); |
518 for (var j = 0; j < subSelectors.length; j++) | 525 for (var j = 0; j < subSelectors.length; j++) |
519 preparedSelectors.push("::content " + subSelectors[j]); | 526 preparedSelectors.push("::content " + subSelectors[j]); |
520 } | 527 } |
521 selectors = preparedSelectors; | 528 selectors = preparedSelectors; |
522 } | 529 } |
523 | 530 |
524 // Safari only allows 8192 primitive selectors to be injected at once[1], we | 531 // Safari only allows 8192 primitive selectors to be injected at once[1], we |
525 // therefore chunk the inserted selectors into groups of 200 to be safe. | 532 // therefore chunk the inserted selectors into groups of 200 to be safe. |
526 // (Chrome also has a limit, larger... but we're not certain exactly what it | 533 // (Chrome also has a limit, larger... but we're not certain exactly what it |
527 // is! Edge apparently has no such limit.) | 534 // is! Edge apparently has no such limit.) |
528 // [1] - https://github.com/WebKit/webkit/blob/1cb2227f6b2a1035f7bdc46e5ab69
debb75fc1de/Source/WebCore/css/RuleSet.h#L68 | 535 // [1] - https://github.com/WebKit/webkit/blob/1cb2227f6b2a1035f7bdc46e5ab69
debb75fc1de/Source/WebCore/css/RuleSet.h#L68 |
529 for (var i = 0; i < selectors.length; i += SELECTOR_GROUP_SIZE) | 536 for (var i = 0; i < selectors.length; i += this.selectorGroupSize) |
530 { | 537 { |
531 var selector = selectors.slice(i, i + SELECTOR_GROUP_SIZE).join(", "); | 538 var selector = selectors.slice(i, i + this.selectorGroupSize).join(", "); |
532 style.sheet.addRule(selector, "display: none !important;"); | 539 this.style.sheet.addRule(selector, "display: none !important;"); |
533 } | 540 } |
534 }; | 541 }, |
535 | 542 |
536 var updateStylesheet = function() | 543 apply: function() |
537 { | 544 { |
538 var selectors = null; | 545 var selectors = null; |
539 var CSSPropertyFiltersLoaded = false; | 546 var propertyFiltersLoaded = false; |
540 | 547 |
541 var checkLoaded = function() | 548 var checkLoaded = function() |
542 { | 549 { |
543 if (!selectors || !CSSPropertyFiltersLoaded) | 550 if (!selectors || !propertyFiltersLoaded) |
544 return; | 551 return; |
545 | 552 |
546 if (observer) | 553 if (this.tracer) |
547 observer.disconnect(); | 554 this.tracer.disconnect(); |
548 observer = null; | 555 this.tracer = null; |
549 | 556 |
550 if (tracer) | 557 if (this.style && this.style.parentElement) |
551 tracer.disconnect(); | 558 this.style.parentElement.removeChild(this.style); |
552 tracer = null; | 559 this.style = null; |
553 | 560 |
554 if (style && style.parentElement) | 561 this.addSelectors(selectors.selectors); |
555 style.parentElement.removeChild(style); | 562 this.propertyFilters.apply(); |
556 style = null; | |
557 | |
558 addElemHideSelectors(selectors.selectors); | |
559 propertyFilters.apply(); | |
560 | 563 |
561 if (selectors.trace) | 564 if (selectors.trace) |
562 tracer = new ElementHidingTracer(selectors.selectors); | 565 this.tracer = new ElementHidingTracer(selectors.selectors); |
563 }; | 566 }.bind(this); |
564 | 567 |
565 ext.backgroundPage.sendMessage({type: "get-selectors"}, function(response) | 568 ext.backgroundPage.sendMessage({type: "get-selectors"}, function(response) |
566 { | 569 { |
567 selectors = response; | 570 selectors = response; |
568 checkLoaded(); | 571 checkLoaded(); |
569 }); | 572 }); |
570 | 573 |
571 propertyFilters.load(function() | 574 this.propertyFilters.load(function() |
572 { | 575 { |
573 CSSPropertyFiltersLoaded = true; | 576 propertyFiltersLoaded = true; |
574 checkLoaded(); | 577 checkLoaded(); |
575 }); | 578 }); |
576 }; | 579 } |
577 | 580 }; |
578 updateStylesheet(); | 581 |
| 582 if (document instanceof HTMLDocument) |
| 583 { |
| 584 checkSitekey(); |
| 585 wrapWebSocket(); |
| 586 |
| 587 var elemhide = new ElemHide(); |
| 588 elemhide.apply(); |
579 | 589 |
580 document.addEventListener("error", function(event) | 590 document.addEventListener("error", function(event) |
581 { | 591 { |
582 checkCollapse(event.target); | 592 checkCollapse(event.target); |
583 }, true); | 593 }, true); |
584 | 594 |
585 document.addEventListener("load", function(event) | 595 document.addEventListener("load", function(event) |
586 { | 596 { |
587 var element = event.target; | 597 var element = event.target; |
588 if (/^i?frame$/.test(element.localName)) | 598 if (/^i?frame$/.test(element.localName)) |
589 checkCollapse(element); | 599 checkCollapse(element); |
590 }, true); | 600 }, true); |
591 | 601 } |
592 return updateStylesheet; | |
593 } | |
594 | |
595 if (document instanceof HTMLDocument) | |
596 { | |
597 checkSitekey(); | |
598 window.updateStylesheet = init(); | |
599 } | |
LEFT | RIGHT |