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-2017 eyeo GmbH | 3 * Copyright (C) 2006-2017 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 315 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
326 }, | 326 }, |
327 | 327 |
328 disconnect() | 328 disconnect() |
329 { | 329 { |
330 document.removeEventListener("DOMContentLoaded", this.trace); | 330 document.removeEventListener("DOMContentLoaded", this.trace); |
331 this.observer.disconnect(); | 331 this.observer.disconnect(); |
332 clearTimeout(this.timeout); | 332 clearTimeout(this.timeout); |
333 } | 333 } |
334 }; | 334 }; |
335 | 335 |
336 function runInPageContext(fn, arg) | |
337 { | |
338 let script = document.createElement("script"); | |
339 script.type = "application/javascript"; | |
340 script.async = false; | |
341 script.textContent = "(" + fn + ")(" + JSON.stringify(arg) + ");"; | |
342 document.documentElement.appendChild(script); | |
343 document.documentElement.removeChild(script); | |
344 } | |
345 | |
346 function wrapRequestAPIs() | |
347 { | |
348 let randomEventName = "abp-request-" + Math.random().toString(36).substr(2); | |
349 | |
350 // Proxy "should we block?" messages from checkRequest inside the injected | |
351 // code to the background page and back again. | |
352 document.addEventListener(randomEventName, event => | |
353 { | |
354 let {url, requestType} = event.detail; | |
355 | |
356 ext.backgroundPage.sendMessage({ | |
357 type: "request.blockedByWrapper", | |
358 requestType, | |
359 url | |
360 }, block => | |
361 { | |
362 document.dispatchEvent(new CustomEvent( | |
363 randomEventName + "-" + requestType + "-" + url, {detail: block} | |
364 )); | |
365 }); | |
366 }); | |
367 | |
368 runInPageContext(eventName => | |
369 { | |
370 let RealCustomEvent = window.CustomEvent; | |
371 let addEventListener = document.addEventListener.bind(document); | |
372 let removeEventListener = document.removeEventListener.bind(document); | |
373 let dispatchEvent = document.dispatchEvent.bind(document); | |
374 | |
375 function checkRequest(requestType, url, callback) | |
376 { | |
377 let incomingEventName = eventName + "-" + requestType + "-" + url; | |
378 | |
379 function listener(event) | |
380 { | |
381 callback(event.detail); | |
382 removeEventListener(incomingEventName, listener); | |
383 } | |
384 addEventListener(incomingEventName, listener); | |
385 | |
386 dispatchEvent(new RealCustomEvent(eventName, | |
387 {detail: {url, requestType}})); | |
388 } | |
389 | |
390 // Only to be called before the page's code, not hardened. | |
391 function copyProperties(src, dest, properties) | |
392 { | |
393 for (let name of properties) | |
394 { | |
395 Object.defineProperty(dest, name, | |
396 Object.getOwnPropertyDescriptor(src, name)); | |
397 } | |
398 } | |
399 | |
400 /* | |
401 * WebSocket wrapper | |
402 * | |
403 * Required before Chrome 58, since the webRequest API didn't allow us to | |
404 * intercept WebSockets. | |
405 * See https://bugs.chromium.org/p/chromium/issues/detail?id=129353 | |
406 */ | |
407 let RealWebSocket = WebSocket; | |
408 let closeWebSocket = Function.prototype.call.bind( | |
409 RealWebSocket.prototype.close | |
410 ); | |
411 | |
412 function WrappedWebSocket(url, ...args) | |
413 { | |
414 // Throw correct exceptions if the constructor is used improperly. | |
415 if (!(this instanceof WrappedWebSocket)) return RealWebSocket(); | |
416 if (arguments.length < 1) return new RealWebSocket(); | |
417 | |
418 let websocket = new RealWebSocket(url, ...args); | |
419 | |
420 checkRequest("WEBSOCKET", websocket.url, blocked => | |
421 { | |
422 if (blocked) | |
423 closeWebSocket(websocket); | |
424 }); | |
425 | |
426 return websocket; | |
427 } | |
428 WrappedWebSocket.prototype = RealWebSocket.prototype; | |
429 window.WebSocket = WrappedWebSocket.bind(); | |
430 copyProperties(RealWebSocket, WebSocket, | |
431 ["CONNECTING", "OPEN", "CLOSING", "CLOSED", "prototype"]); | |
432 RealWebSocket.prototype.constructor = WebSocket; | |
433 | |
434 /* | |
435 * RTCPeerConnection wrapper | |
436 * | |
437 * The webRequest API in Chrome does not yet allow the blocking of | |
438 * WebRTC connections. | |
439 * See https://bugs.chromium.org/p/chromium/issues/detail?id=707683 | |
440 */ | |
441 let RealRTCPeerConnection = window.RTCPeerConnection || | |
442 window.webkitRTCPeerConnection; | |
443 let closeRTCPeerConnection = Function.prototype.call.bind( | |
444 RealRTCPeerConnection.prototype.close | |
445 ); | |
446 let RealArray = Array; | |
447 let RealString = String; | |
448 let {create: createObject, defineProperty} = Object; | |
449 | |
450 function normalizeUrl(url) | |
451 { | |
452 if (typeof url != "undefined") | |
453 return RealString(url); | |
454 } | |
455 | |
456 function safeCopyArray(originalArray, transform) | |
457 { | |
458 if (originalArray == null || typeof originalArray != "object") | |
459 return originalArray; | |
460 | |
461 let safeArray = RealArray(originalArray.length); | |
462 for (let i = 0; i < safeArray.length; i++) | |
463 { | |
464 defineProperty(safeArray, i, { | |
465 configurable: false, enumerable: false, writable: false, | |
466 value: transform(originalArray[i]) | |
467 }); | |
468 } | |
469 defineProperty(safeArray, "length", { | |
470 configurable: false, enumerable: false, writable: false, | |
471 value: safeArray.length | |
472 }); | |
473 return safeArray; | |
474 } | |
475 | |
476 // It would be much easier to use the .getConfiguration method to obtain | |
477 // the normalized and safe configuration from the RTCPeerConnection | |
478 // instance. Unfortunately its not implemented as of Chrome unstable 59. | |
479 // See https://www.chromestatus.com/feature/5271355306016768 | |
480 function protectConfiguration(configuration) | |
481 { | |
482 if (configuration == null || typeof configuration != "object") | |
483 return configuration; | |
484 | |
485 let iceServers = safeCopyArray( | |
486 configuration.iceServers, | |
487 iceServer => | |
488 { | |
489 let {url, urls} = iceServer; | |
490 | |
491 // RTCPeerConnection doesn't iterate through pseudo Arrays of urls. | |
492 if (typeof urls != "undefined" && !(urls instanceof RealArray)) | |
493 urls = [urls]; | |
494 | |
495 return createObject(iceServer, { | |
496 url: { | |
497 configurable: false, enumerable: false, writable: false, | |
498 value: normalizeUrl(url) | |
499 }, | |
500 urls: { | |
501 configurable: false, enumerable: false, writable: false, | |
502 value: safeCopyArray(urls, normalizeUrl) | |
503 } | |
504 }); | |
505 } | |
506 ); | |
507 | |
508 return createObject(configuration, { | |
509 iceServers: { | |
510 configurable: false, enumerable: false, writable: false, | |
511 value: iceServers | |
512 } | |
513 }); | |
514 } | |
515 | |
516 function checkUrl(peerconnection, url) | |
517 { | |
518 checkRequest("WEBRTC", url, blocked => | |
519 { | |
520 if (blocked) | |
521 { | |
522 // Calling .close() throws if already closed. | |
523 try | |
524 { | |
525 closeRTCPeerConnection(peerconnection); | |
526 } | |
527 catch (e) {} | |
528 } | |
529 }); | |
530 } | |
531 | |
532 function checkConfiguration(peerconnection, configuration) | |
533 { | |
534 if (configuration && configuration.iceServers) | |
535 { | |
536 for (let i = 0; i < configuration.iceServers.length; i++) | |
537 { | |
538 let iceServer = configuration.iceServers[i]; | |
539 if (iceServer) | |
540 { | |
541 if (iceServer.url) | |
542 checkUrl(peerconnection, iceServer.url); | |
543 | |
544 if (iceServer.urls) | |
545 { | |
546 for (let j = 0; j < iceServer.urls.length; j++) | |
547 checkUrl(peerconnection, iceServer.urls[j]); | |
548 } | |
549 } | |
550 } | |
551 } | |
552 } | |
553 | |
554 // Chrome unstable (tested with 59) has already implemented | |
555 // setConfiguration, so we need to wrap that if it exists too. | |
556 // https://www.chromestatus.com/feature/5596193748942848 | |
557 if (RealRTCPeerConnection.prototype.setConfiguration) | |
558 { | |
559 let realSetConfiguration = Function.prototype.call.bind( | |
560 RealRTCPeerConnection.prototype.setConfiguration | |
561 ); | |
562 | |
563 RealRTCPeerConnection.prototype.setConfiguration = function(configuration) | |
564 { | |
565 configuration = protectConfiguration(configuration); | |
566 | |
567 // Call the real method first, so that validates the configuration for | |
568 // us. Also we might as well since checkRequest is asynchronous anyway. | |
569 realSetConfiguration(this, configuration); | |
570 checkConfiguration(this, configuration); | |
571 }; | |
572 } | |
573 | |
574 function WrappedRTCPeerConnection(...args) | |
575 { | |
576 if (!(this instanceof WrappedRTCPeerConnection)) | |
577 return WrappedRTCPeerConnection(); | |
578 | |
579 let configuration = protectConfiguration(args[0]); | |
580 // Since the old webkitRTCPeerConnection constructor takes an optional | |
581 // second argument we need to take care to pass that through. Necessary | |
582 // for older versions of Chrome such as 49. | |
583 let peerconnection = new RealRTCPeerConnection(configuration, args[1]); | |
584 checkConfiguration(peerconnection, configuration); | |
585 return peerconnection; | |
586 } | |
587 | |
588 WrappedRTCPeerConnection.prototype = RealRTCPeerConnection.prototype; | |
589 | |
590 let boundWrappedRTCPeerConnection = WrappedRTCPeerConnection.bind(); | |
591 copyProperties(RealRTCPeerConnection, boundWrappedRTCPeerConnection, | |
592 ["caller", "generateCertificate", "name", "prototype"]); | |
593 RealRTCPeerConnection.prototype.constructor = boundWrappedRTCPeerConnection; | |
594 | |
595 if ("RTCPeerConnection" in window) | |
596 window.RTCPeerConnection = boundWrappedRTCPeerConnection; | |
597 if ("webkitRTCPeerConnection" in window) | |
598 window.webkitRTCPeerConnection = boundWrappedRTCPeerConnection; | |
599 }, randomEventName); | |
600 } | |
601 | |
602 function ElemHide() | 336 function ElemHide() |
603 { | 337 { |
604 this.shadow = this.createShadowTree(); | 338 this.shadow = this.createShadowTree(); |
605 this.style = null; | 339 this.style = null; |
606 this.tracer = null; | 340 this.tracer = null; |
607 | 341 |
608 this.elemHideEmulation = new ElemHideEmulation( | 342 this.elemHideEmulation = new ElemHideEmulation( |
609 window, | 343 window, |
610 callback => | 344 callback => |
611 { | 345 { |
(...skipping 22 matching lines...) Expand all Loading... |
634 if (/\.(?:google|blogger)\.com$/.test(document.domain)) | 368 if (/\.(?:google|blogger)\.com$/.test(document.domain)) |
635 return null; | 369 return null; |
636 | 370 |
637 // Finally since some users have both AdBlock and Adblock Plus installed we | 371 // Finally since some users have both AdBlock and Adblock Plus installed we |
638 // have to consider how the two extensions interact. For example we want to | 372 // have to consider how the two extensions interact. For example we want to |
639 // avoid creating the shadowRoot twice. | 373 // avoid creating the shadowRoot twice. |
640 let shadow = document.documentElement.shadowRoot || | 374 let shadow = document.documentElement.shadowRoot || |
641 document.documentElement.createShadowRoot(); | 375 document.documentElement.createShadowRoot(); |
642 shadow.appendChild(document.createElement("shadow")); | 376 shadow.appendChild(document.createElement("shadow")); |
643 | 377 |
644 // Stop the website from messing with our shadow root (#4191, #4298). | |
645 if ("shadowRoot" in Element.prototype) | |
646 { | |
647 runInPageContext(() => | |
648 { | |
649 let ourShadowRoot = document.documentElement.shadowRoot; | |
650 if (!ourShadowRoot) | |
651 return; | |
652 let desc = Object.getOwnPropertyDescriptor(Element.prototype, | |
653 "shadowRoot"); | |
654 let shadowRoot = Function.prototype.call.bind(desc.get); | |
655 | |
656 Object.defineProperty(Element.prototype, "shadowRoot", { | |
657 configurable: true, enumerable: true, get() | |
658 { | |
659 let thisShadow = shadowRoot(this); | |
660 return thisShadow == ourShadowRoot ? null : thisShadow; | |
661 } | |
662 }); | |
663 }, null); | |
664 } | |
665 | |
666 return shadow; | 378 return shadow; |
667 }, | 379 }, |
668 | 380 |
669 addSelectors(selectors, filters) | 381 addSelectors(selectors, filters) |
670 { | 382 { |
671 if (selectors.length == 0) | 383 if (selectors.length == 0) |
672 return; | 384 return; |
673 | 385 |
674 if (!this.style) | 386 if (!this.style) |
675 { | 387 { |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
742 | 454 |
743 this.addSelectors(response.selectors); | 455 this.addSelectors(response.selectors); |
744 this.elemHideEmulation.apply(); | 456 this.elemHideEmulation.apply(); |
745 }); | 457 }); |
746 } | 458 } |
747 }; | 459 }; |
748 | 460 |
749 if (document instanceof HTMLDocument) | 461 if (document instanceof HTMLDocument) |
750 { | 462 { |
751 checkSitekey(); | 463 checkSitekey(); |
752 wrapRequestAPIs(); | |
753 | 464 |
754 elemhide = new ElemHide(); | 465 elemhide = new ElemHide(); |
755 elemhide.apply(); | 466 elemhide.apply(); |
756 | 467 |
757 document.addEventListener("error", event => | 468 document.addEventListener("error", event => |
758 { | 469 { |
759 checkCollapse(event.target); | 470 checkCollapse(event.target); |
760 }, true); | 471 }, true); |
761 | 472 |
762 document.addEventListener("load", event => | 473 document.addEventListener("load", event => |
763 { | 474 { |
764 let element = event.target; | 475 let element = event.target; |
765 if (/^i?frame$/.test(element.localName)) | 476 if (/^i?frame$/.test(element.localName)) |
766 checkCollapse(element); | 477 checkCollapse(element); |
767 }, true); | 478 }, true); |
768 } | 479 } |
OLD | NEW |