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