Index: include.preload.js |
diff --git a/include.preload.js b/include.preload.js |
index 949d039663f9130843b4d8d0a3ddfcb434522066..f9df40ee5f39bc77c410f1394b92374288dac15b 100644 |
--- a/include.preload.js |
+++ b/include.preload.js |
@@ -17,6 +17,7 @@ |
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver; |
var SELECTOR_GROUP_SIZE = 200; |
+var id = Math.random().toString(36).substr(2); |
var typeMap = { |
"img": "IMAGE", |
@@ -349,49 +350,138 @@ function reinjectStyleSheetWhenRemoved(document, style) |
return observer; |
} |
+function runInPage(fn, arg) |
+{ |
+ var script = document.createElement("script"); |
+ script.type = "application/javascript"; |
+ script.async = false; |
+ |
+ // include.youtube.js passes this function a RegExp which JSON.stringify would |
+ // convert to "{}". |
+ if (!(arg instanceof RegExp)) |
+ arg = JSON.stringify(arg); |
+ |
+ script.textContent = "(" + fn + ")(" + arg + ");"; |
+ document.documentElement.appendChild(script); |
+ document.documentElement.removeChild(script); |
+} |
+ |
function protectStyleSheet(document, style) |
{ |
- var id = Math.random().toString(36).substr(2) |
style.id = id; |
- var code = [ |
- "(function()", |
- "{", |
- ' var style = document.getElementById("' + id + '") ||', |
- ' document.documentElement.shadowRoot.getElementById("' + id + '");', |
- ' style.removeAttribute("id");' |
- ]; |
+ runInPage(function(id) |
+ { |
+ var style = document.getElementById(id) || |
+ document.documentElement.shadowRoot.getElementById(id); |
+ style.removeAttribute("id"); |
+ |
+ var disableables = [style, style.sheet]; |
+ for (var i = 0; i < disableables.length; i++) |
+ Object.defineProperty(disableables[i], "disabled", |
+ {value: false, enumerable: true}); |
- var disableables = ["style", "style.sheet"]; |
- for (var i = 0; i < disableables.length; i++) |
+ var boundCall = Function.prototype.call.bind(Function.prototype.call); |
+ ["deleteRule", "removeRule"].forEach(function(method) |
+ { |
+ var original = CSSStyleSheet.prototype[method]; |
+ CSSStyleSheet.prototype[method] = function(index) |
+ { |
+ if (this != style.sheet) |
+ boundCall(original, this, index); |
+ }; |
+ }); |
+ }, id); |
+} |
+ |
+// Neither Chrome[1] nor Safari allow us to intercept WebSockets, and therefore |
+// some ad networks are misusing them as a way to serve adverts and circumvent |
+// us. As a workaround we wrap WebSocket, preventing blocked WebSocket |
+// connections from being opened. |
+// [1] - https://bugs.chromium.org/p/chromium/issues/detail?id=129353 |
+function wrapWebSocket() |
+{ |
+ if (typeof WebSocket == "undefined") |
+ return; |
+ |
+ var eventName = "abpws-" + id; |
+ |
+ document.addEventListener(eventName, function(event) |
{ |
- code.push(" Object.defineProperty(" + disableables[i] + ', "disabled", ' |
- + "{value: false, enumerable: true});"); |
- } |
+ ext.backgroundPage.sendMessage({ |
+ type: "websocket-request", |
+ url: event.detail.url |
+ }, function (block) |
+ { |
+ document.dispatchEvent( |
+ new CustomEvent(eventName + "-" + event.detail.url, {detail: block}) |
+ ); |
+ }); |
+ }); |
- var methods = ["deleteRule", "removeRule"]; |
- for (var j = 0; j < methods.length; j++) |
+ runInPage(function(eventName) |
{ |
- var method = methods[j]; |
- if (method in CSSStyleSheet.prototype) |
+ // As far as possible we must track everything we use that could be |
+ // sabotaged by the website later in order to circumvent us. |
+ var RealWebSocket = WebSocket; |
+ var closeWebSocket = RealWebSocket.prototype.close; |
+ var addEventListener = document.addEventListener.bind(document); |
+ var removeEventListener = document.removeEventListener.bind(document); |
+ var dispatchEvent = document.dispatchEvent.bind(document); |
+ var CustomEvent = window.CustomEvent; |
+ var boundCall = Function.prototype.call.bind(Function.prototype.call); |
+ var functionToString = Function.prototype.toString; |
+ // (Safari 9 considers WebSocket to be an object rather than a function.) |
+ var webSocketString = RealWebSocket.toString(); |
+ |
+ function checkRequest(url, callback) |
{ |
- var origin = "CSSStyleSheet.prototype." + method; |
- code.push(" var " + method + " = " + origin + ";", |
- " " + origin + " = function(index)", |
- " {", |
- " if (this != style.sheet)", |
- " " + method + ".call(this, index);", |
- " }"); |
+ var incomingEventName = eventName + "-" + url; |
+ function listener(event) |
+ { |
+ callback(event.detail); |
+ removeEventListener(incomingEventName, listener); |
+ } |
+ addEventListener(incomingEventName, listener); |
+ |
+ dispatchEvent(new CustomEvent(eventName, { |
+ detail: {url: url} |
+ })); |
} |
- } |
- code.push("})();"); |
+ function wrappedToString() |
+ { |
+ if (this === WebSocket) |
+ return webSocketString; |
+ if (this === wrappedToString) |
+ return boundCall(functionToString, functionToString); |
+ return boundCall(functionToString, this); |
+ }; |
+ Function.prototype.toString = wrappedToString; |
- var script = document.createElement("script"); |
- script.async = false; |
- script.textContent = code.join("\n"); |
- document.documentElement.appendChild(script); |
- document.documentElement.removeChild(script); |
+ WebSocket = function(url, protocols) |
+ { |
+ var websocket = new RealWebSocket(url, protocols); |
+ |
+ checkRequest(websocket.url, function(blocked) |
+ { |
+ if (blocked) |
+ boundCall(closeWebSocket, websocket); |
+ }); |
+ |
+ return websocket; |
+ }; |
+ |
+ var properties = Object.getOwnPropertyNames(RealWebSocket); |
+ for (var i = 0; i < properties.length; i++) |
+ { |
+ var name = properties[i]; |
+ var desc = Object.getOwnPropertyDescriptor(RealWebSocket, name); |
+ Object.defineProperty(WebSocket, name, desc); |
+ } |
+ |
+ RealWebSocket.prototype.constructor = WebSocket; |
+ }, eventName); |
} |
function init(document) |
@@ -401,6 +491,8 @@ function init(document) |
var observer = null; |
var tracer = null; |
+ wrapWebSocket(); |
+ |
function getPropertyFilters(callback) |
{ |
ext.backgroundPage.sendMessage({ |