| 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 |