Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Unified Diff: inject.preload.js

Issue 29437555: Issue 4586, 5207 - Move the injected wrappers into a separate file (Closed)
Patch Set: Avoid adding an extra content script Created May 12, 2017, 12:53 p.m.
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « include.preload.js ('k') | metadata.chrome » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: inject.preload.js
diff --git a/inject.preload.js b/inject.preload.js
new file mode 100644
index 0000000000000000000000000000000000000000..d3838ea78aa1aef98b9dea0948877e40566ef7b0
--- /dev/null
+++ b/inject.preload.js
@@ -0,0 +1,311 @@
+/*
+ * This file is part of Adblock Plus <https://adblockplus.org/>,
+ * Copyright (C) 2006-2017 eyeo GmbH
+ *
+ * Adblock Plus is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Adblock Plus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+"use strict";
+
+let randomEventName = "abp-request-" + Math.random().toString(36).substr(2);
+
+// Proxy "should we block?" messages from checkRequest inside the injected
+// code to the background page and back again.
+document.addEventListener(randomEventName, event =>
+{
+ let {url, requestType} = event.detail;
+
+ ext.backgroundPage.sendMessage({
+ type: "request.blockedByWrapper",
+ requestType,
+ url
+ }, block =>
+ {
+ document.dispatchEvent(new CustomEvent(
+ randomEventName + "-" + requestType + "-" + url, {detail: block}
+ ));
+ });
+});
+
+let injected = function(eventName)
+{
+ /*
+ * Shadow root getter wrapper
+ *
+ * After creating our shadowRoot we must wrap the getter to prevent the
+ * website from accessing it (#4191, #4298). This is required as a
+ * workaround for the lack of user style support in Chrome.
+ * See https://bugs.chromium.org/p/chromium/issues/detail?id=632009&desc=2
+ */
+ if ("shadowRoot" in Element.prototype)
+ {
+ let ourShadowRoot = document.documentElement.shadowRoot;
+ if (ourShadowRoot)
+ {
+ let desc = Object.getOwnPropertyDescriptor(Element.prototype,
+ "shadowRoot");
+ let shadowRoot = Function.prototype.call.bind(desc.get);
+
+ Object.defineProperty(Element.prototype, "shadowRoot", {
+ configurable: true, enumerable: true, get()
+ {
+ let thisShadow = shadowRoot(this);
+ return thisShadow == ourShadowRoot ? null : thisShadow;
+ }
+ });
+ }
+ }
+
+ /*
+ * Shared request checking code, used by both the WebSocket and
+ * RTCPeerConnection wrappers.
+ */
+ let RealCustomEvent = window.CustomEvent;
+ let addEventListener = document.addEventListener.bind(document);
+ let removeEventListener = document.removeEventListener.bind(document);
+ let dispatchEvent = document.dispatchEvent.bind(document);
+
+ function checkRequest(requestType, url, callback)
+ {
+ let incomingEventName = eventName + "-" + requestType + "-" + url;
+
+ function listener(event)
+ {
+ callback(event.detail);
+ removeEventListener(incomingEventName, listener);
+ }
+ addEventListener(incomingEventName, listener);
+
+ dispatchEvent(new RealCustomEvent(eventName, {detail: {url, requestType}}));
+ }
+
+ // Only to be called before the page's code, not hardened.
+ function copyProperties(src, dest, properties)
+ {
+ for (let name of properties)
+ {
+ Object.defineProperty(dest, name,
+ Object.getOwnPropertyDescriptor(src, name));
+ }
+ }
+
+ /*
+ * WebSocket wrapper
+ *
+ * Required before Chrome 58, since the webRequest API didn't allow us to
+ * intercept WebSockets.
+ * See https://bugs.chromium.org/p/chromium/issues/detail?id=129353
+ */
+ let RealWebSocket = WebSocket;
+ let closeWebSocket = Function.prototype.call.bind(
+ RealWebSocket.prototype.close
+ );
+
+ function WrappedWebSocket(url, ...args)
+ {
+ // Throw correct exceptions if the constructor is used improperly.
+ if (!(this instanceof WrappedWebSocket)) return RealWebSocket();
+ if (arguments.length < 1) return new RealWebSocket();
+
+ let websocket = new RealWebSocket(url, ...args);
+
+ checkRequest("WEBSOCKET", websocket.url, blocked =>
+ {
+ if (blocked)
+ closeWebSocket(websocket);
+ });
+
+ return websocket;
+ }
+ WrappedWebSocket.prototype = RealWebSocket.prototype;
+ window.WebSocket = WrappedWebSocket.bind();
+ copyProperties(RealWebSocket, WebSocket,
+ ["CONNECTING", "OPEN", "CLOSING", "CLOSED", "prototype"]);
+ RealWebSocket.prototype.constructor = WebSocket;
+
+ /*
+ * RTCPeerConnection wrapper
+ *
+ * The webRequest API in Chrome does not yet allow the blocking of
+ * WebRTC connections.
+ * See https://bugs.chromium.org/p/chromium/issues/detail?id=707683
+ */
+ let RealRTCPeerConnection = window.RTCPeerConnection ||
+ window.webkitRTCPeerConnection;
+ let closeRTCPeerConnection = Function.prototype.call.bind(
+ RealRTCPeerConnection.prototype.close
+ );
+ let RealArray = Array;
+ let RealString = String;
+ let {create: createObject, defineProperty} = Object;
+
+ function normalizeUrl(url)
+ {
+ if (typeof url != "undefined")
+ return RealString(url);
+ }
+
+ function safeCopyArray(originalArray, transform)
+ {
+ if (originalArray == null || typeof originalArray != "object")
+ return originalArray;
+
+ let safeArray = RealArray(originalArray.length);
+ for (let i = 0; i < safeArray.length; i++)
+ {
+ defineProperty(safeArray, i, {
+ configurable: false, enumerable: false, writable: false,
+ value: transform(originalArray[i])
+ });
+ }
+ defineProperty(safeArray, "length", {
+ configurable: false, enumerable: false, writable: false,
+ value: safeArray.length
+ });
+ return safeArray;
+ }
+
+ // It would be much easier to use the .getConfiguration method to obtain
+ // the normalized and safe configuration from the RTCPeerConnection
+ // instance. Unfortunately its not implemented as of Chrome unstable 59.
+ // See https://www.chromestatus.com/feature/5271355306016768
+ function protectConfiguration(configuration)
+ {
+ if (configuration == null || typeof configuration != "object")
+ return configuration;
+
+ let iceServers = safeCopyArray(
+ configuration.iceServers,
+ iceServer =>
+ {
+ let {url, urls} = iceServer;
+
+ // RTCPeerConnection doesn't iterate through pseudo Arrays of urls.
+ if (typeof urls != "undefined" && !(urls instanceof RealArray))
+ urls = [urls];
+
+ return createObject(iceServer, {
+ url: {
+ configurable: false, enumerable: false, writable: false,
+ value: normalizeUrl(url)
+ },
+ urls: {
+ configurable: false, enumerable: false, writable: false,
+ value: safeCopyArray(urls, normalizeUrl)
+ }
+ });
+ }
+ );
+
+ return createObject(configuration, {
+ iceServers: {
+ configurable: false, enumerable: false, writable: false,
+ value: iceServers
+ }
+ });
+ }
+
+ function checkUrl(peerconnection, url)
+ {
+ checkRequest("WEBRTC", url, blocked =>
+ {
+ if (blocked)
+ {
+ // Calling .close() throws if already closed.
+ try
+ {
+ closeRTCPeerConnection(peerconnection);
+ }
+ catch (e) {}
+ }
+ });
+ }
+
+ function checkConfiguration(peerconnection, configuration)
+ {
+ if (configuration && configuration.iceServers)
+ {
+ for (let i = 0; i < configuration.iceServers.length; i++)
+ {
+ let iceServer = configuration.iceServers[i];
+ if (iceServer)
+ {
+ if (iceServer.url)
+ checkUrl(peerconnection, iceServer.url);
+
+ if (iceServer.urls)
+ {
+ for (let j = 0; j < iceServer.urls.length; j++)
+ checkUrl(peerconnection, iceServer.urls[j]);
+ }
+ }
+ }
+ }
+ }
+
+ // Chrome unstable (tested with 59) has already implemented
+ // setConfiguration, so we need to wrap that if it exists too.
+ // https://www.chromestatus.com/feature/5596193748942848
+ if (RealRTCPeerConnection.prototype.setConfiguration)
+ {
+ let realSetConfiguration = Function.prototype.call.bind(
+ RealRTCPeerConnection.prototype.setConfiguration
+ );
+
+ RealRTCPeerConnection.prototype.setConfiguration = function(configuration)
+ {
+ configuration = protectConfiguration(configuration);
+
+ // Call the real method first, so that validates the configuration for
+ // us. Also we might as well since checkRequest is asynchronous anyway.
+ realSetConfiguration(this, configuration);
+ checkConfiguration(this, configuration);
+ };
+ }
+
+ function WrappedRTCPeerConnection(...args)
+ {
+ if (!(this instanceof WrappedRTCPeerConnection))
+ return WrappedRTCPeerConnection();
+
+ let configuration = protectConfiguration(args[0]);
+ // Since the old webkitRTCPeerConnection constructor takes an optional
+ // second argument we need to take care to pass that through. Necessary
+ // for older versions of Chrome such as 49.
+ let peerconnection = new RealRTCPeerConnection(configuration, args[1]);
+ checkConfiguration(peerconnection, configuration);
+ return peerconnection;
+ }
+
+ WrappedRTCPeerConnection.prototype = RealRTCPeerConnection.prototype;
+
+ let boundWrappedRTCPeerConnection = WrappedRTCPeerConnection.bind();
+ copyProperties(RealRTCPeerConnection, boundWrappedRTCPeerConnection,
+ ["caller", "generateCertificate", "name", "prototype"]);
+ RealRTCPeerConnection.prototype.constructor = boundWrappedRTCPeerConnection;
+
+ if ("RTCPeerConnection" in window)
+ window.RTCPeerConnection = boundWrappedRTCPeerConnection;
+ if ("webkitRTCPeerConnection" in window)
+ window.webkitRTCPeerConnection = boundWrappedRTCPeerConnection;
+};
+
+if (document instanceof HTMLDocument)
+{
+ let script = document.createElement("script");
+ script.type = "application/javascript";
+ script.async = false;
+ script.textContent = "(" + injected + ")('" + randomEventName + "');";
+ document.documentElement.appendChild(script);
+ document.documentElement.removeChild(script);
+}
« no previous file with comments | « include.preload.js ('k') | metadata.chrome » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld