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

Side by Side Diff: inject.preload.js

Issue 29452181: Noissue - Merge current tip to Edge bookmark (Closed)
Patch Set: Created May 30, 2017, 3:49 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « include.preload.js ('k') | lib/compat.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /*
2 * This file is part of Adblock Plus <https://adblockplus.org/>,
3 * Copyright (C) 2006-2017 eyeo GmbH
4 *
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
7 * published by the Free Software Foundation.
8 *
9 * Adblock Plus is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
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/>.
16 */
17
18 "use strict";
19
20 let randomEventName = "abp-request-" + Math.random().toString(36).substr(2);
21
22 // Proxy "should we block?" messages from checkRequest inside the injected
23 // code to the background page and back again.
24 document.addEventListener(randomEventName, event =>
25 {
26 let {url, requestType} = event.detail;
27
28 ext.backgroundPage.sendMessage({
29 type: "request.blockedByWrapper",
30 requestType,
31 url
32 }, block =>
33 {
34 document.dispatchEvent(new CustomEvent(
35 randomEventName + "-" + requestType + "-" + url, {detail: block}
36 ));
37 });
38 });
39
40 function injected(eventName, injectedIntoContentWindow)
41 {
42 let checkRequest;
43
44 /*
45 * Frame context wrapper
46 *
47 * For some edge-cases Chrome will not run content scripts inside of frames.
48 * Website have started to abuse this fact to access unwrapped APIs via a
49 * frame's contentWindow (#4586, 5207). Therefore until Chrome runs content
50 * scripts consistently for all frames we must take care to (re)inject our
51 * wrappers when the contentWindow is accessed.
52 */
53 let injectedToString = Function.prototype.toString.bind(injected);
54 let injectedFrames = new WeakSet();
55 let injectedFramesAdd = WeakSet.prototype.add.bind(injectedFrames);
56 let injectedFramesHas = WeakSet.prototype.has.bind(injectedFrames);
57
58 function injectIntoContentWindow(contentWindow)
59 {
60 if (contentWindow && !injectedFramesHas(contentWindow))
61 {
62 injectedFramesAdd(contentWindow);
63 try
64 {
65 contentWindow[eventName] = checkRequest;
66 contentWindow.eval(
67 "(" + injectedToString() + ")('" + eventName + "', true);"
68 );
69 delete contentWindow[eventName];
70 }
71 catch (e) {}
72 }
73 }
74
75 for (let element of [HTMLFrameElement, HTMLIFrameElement, HTMLObjectElement])
76 {
77 let contentDocumentDesc = Object.getOwnPropertyDescriptor(
78 element.prototype, "contentDocument"
79 );
80 let contentWindowDesc = Object.getOwnPropertyDescriptor(
81 element.prototype, "contentWindow"
82 );
83
84 // Apparently in HTMLObjectElement.prototype.contentWindow does not exist
85 // in older versions of Chrome such as 42.
86 if (!contentWindowDesc)
87 continue;
88
89 let getContentDocument = Function.prototype.call.bind(
90 contentDocumentDesc.get
91 );
92 let getContentWindow = Function.prototype.call.bind(
93 contentWindowDesc.get
94 );
95
96 contentWindowDesc.get = function()
97 {
98 let contentWindow = getContentWindow(this);
99 injectIntoContentWindow(contentWindow);
100 return contentWindow;
101 };
102 contentDocumentDesc.get = function()
103 {
104 injectIntoContentWindow(getContentWindow(this));
105 return getContentDocument(this);
106 };
107 Object.defineProperty(element.prototype, "contentWindow",
108 contentWindowDesc);
109 Object.defineProperty(element.prototype, "contentDocument",
110 contentDocumentDesc);
111 }
112
113 /*
114 * Shadow root getter wrapper
115 *
116 * After creating our shadowRoot we must wrap the getter to prevent the
117 * website from accessing it (#4191, #4298). This is required as a
118 * workaround for the lack of user style support in Chrome.
119 * See https://bugs.chromium.org/p/chromium/issues/detail?id=632009&desc=2
120 */
121 if ("shadowRoot" in Element.prototype)
122 {
123 let ourShadowRoot = document.documentElement.shadowRoot;
124 if (ourShadowRoot)
125 {
126 let desc = Object.getOwnPropertyDescriptor(Element.prototype,
127 "shadowRoot");
128 let shadowRoot = Function.prototype.call.bind(desc.get);
129
130 Object.defineProperty(Element.prototype, "shadowRoot", {
131 configurable: true, enumerable: true, get()
132 {
133 let thisShadow = shadowRoot(this);
134 return thisShadow == ourShadowRoot ? null : thisShadow;
135 }
136 });
137 }
138 }
139
140 /*
141 * Shared request checking code, used by both the WebSocket and
142 * RTCPeerConnection wrappers.
143 */
144 let RealCustomEvent = window.CustomEvent;
145
146 // If we've been injected into a frame via contentWindow then we can simply
147 // grab the copy of checkRequest left for us by the parent document. Otherwise
148 // we need to set it up now, along with the event handling functions.
149 if (injectedIntoContentWindow)
150 checkRequest = window[eventName];
151 else
152 {
153 let addEventListener = document.addEventListener.bind(document);
154 let dispatchEvent = document.dispatchEvent.bind(document);
155 let removeEventListener = document.removeEventListener.bind(document);
156 checkRequest = (requestType, url, callback) =>
157 {
158 let incomingEventName = eventName + "-" + requestType + "-" + url;
159
160 function listener(event)
161 {
162 callback(event.detail);
163 removeEventListener(incomingEventName, listener);
164 }
165 addEventListener(incomingEventName, listener);
166
167 dispatchEvent(new RealCustomEvent(eventName,
168 {detail: {url, requestType}}));
169 };
170 }
171
172 // Only to be called before the page's code, not hardened.
173 function copyProperties(src, dest, properties)
174 {
175 for (let name of properties)
176 {
177 Object.defineProperty(dest, name,
178 Object.getOwnPropertyDescriptor(src, name));
179 }
180 }
181
182 /*
183 * WebSocket wrapper
184 *
185 * Required before Chrome 58, since the webRequest API didn't allow us to
186 * intercept WebSockets.
187 * See https://bugs.chromium.org/p/chromium/issues/detail?id=129353
188 */
189 let RealWebSocket = WebSocket;
190 let closeWebSocket = Function.prototype.call.bind(
191 RealWebSocket.prototype.close
192 );
193
194 function WrappedWebSocket(url, ...args)
195 {
196 // Throw correct exceptions if the constructor is used improperly.
197 if (!(this instanceof WrappedWebSocket)) return RealWebSocket();
198 if (arguments.length < 1) return new RealWebSocket();
199
200 let websocket = new RealWebSocket(url, ...args);
201
202 checkRequest("websocket", websocket.url, blocked =>
203 {
204 if (blocked)
205 closeWebSocket(websocket);
206 });
207
208 return websocket;
209 }
210 WrappedWebSocket.prototype = RealWebSocket.prototype;
211 window.WebSocket = WrappedWebSocket.bind();
212 copyProperties(RealWebSocket, WebSocket,
213 ["CONNECTING", "OPEN", "CLOSING", "CLOSED", "prototype"]);
214 RealWebSocket.prototype.constructor = WebSocket;
215
216 /*
217 * RTCPeerConnection wrapper
218 *
219 * The webRequest API in Chrome does not yet allow the blocking of
220 * WebRTC connections.
221 * See https://bugs.chromium.org/p/chromium/issues/detail?id=707683
222 */
223 let RealRTCPeerConnection = window.RTCPeerConnection ||
224 window.webkitRTCPeerConnection;
225 let closeRTCPeerConnection = Function.prototype.call.bind(
226 RealRTCPeerConnection.prototype.close
227 );
228 let RealArray = Array;
229 let RealString = String;
230 let {create: createObject, defineProperty} = Object;
231
232 function normalizeUrl(url)
233 {
234 if (typeof url != "undefined")
235 return RealString(url);
236 }
237
238 function safeCopyArray(originalArray, transform)
239 {
240 if (originalArray == null || typeof originalArray != "object")
241 return originalArray;
242
243 let safeArray = RealArray(originalArray.length);
244 for (let i = 0; i < safeArray.length; i++)
245 {
246 defineProperty(safeArray, i, {
247 configurable: false, enumerable: false, writable: false,
248 value: transform(originalArray[i])
249 });
250 }
251 defineProperty(safeArray, "length", {
252 configurable: false, enumerable: false, writable: false,
253 value: safeArray.length
254 });
255 return safeArray;
256 }
257
258 // It would be much easier to use the .getConfiguration method to obtain
259 // the normalized and safe configuration from the RTCPeerConnection
260 // instance. Unfortunately its not implemented as of Chrome unstable 59.
261 // See https://www.chromestatus.com/feature/5271355306016768
262 function protectConfiguration(configuration)
263 {
264 if (configuration == null || typeof configuration != "object")
265 return configuration;
266
267 let iceServers = safeCopyArray(
268 configuration.iceServers,
269 iceServer =>
270 {
271 let {url, urls} = iceServer;
272
273 // RTCPeerConnection doesn't iterate through pseudo Arrays of urls.
274 if (typeof urls != "undefined" && !(urls instanceof RealArray))
275 urls = [urls];
276
277 return createObject(iceServer, {
278 url: {
279 configurable: false, enumerable: false, writable: false,
280 value: normalizeUrl(url)
281 },
282 urls: {
283 configurable: false, enumerable: false, writable: false,
284 value: safeCopyArray(urls, normalizeUrl)
285 }
286 });
287 }
288 );
289
290 return createObject(configuration, {
291 iceServers: {
292 configurable: false, enumerable: false, writable: false,
293 value: iceServers
294 }
295 });
296 }
297
298 function checkUrl(peerconnection, url)
299 {
300 checkRequest("webrtc", url, blocked =>
301 {
302 if (blocked)
303 {
304 // Calling .close() throws if already closed.
305 try
306 {
307 closeRTCPeerConnection(peerconnection);
308 }
309 catch (e) {}
310 }
311 });
312 }
313
314 function checkConfiguration(peerconnection, configuration)
315 {
316 if (configuration && configuration.iceServers)
317 {
318 for (let i = 0; i < configuration.iceServers.length; i++)
319 {
320 let iceServer = configuration.iceServers[i];
321 if (iceServer)
322 {
323 if (iceServer.url)
324 checkUrl(peerconnection, iceServer.url);
325
326 if (iceServer.urls)
327 {
328 for (let j = 0; j < iceServer.urls.length; j++)
329 checkUrl(peerconnection, iceServer.urls[j]);
330 }
331 }
332 }
333 }
334 }
335
336 // Chrome unstable (tested with 59) has already implemented
337 // setConfiguration, so we need to wrap that if it exists too.
338 // https://www.chromestatus.com/feature/5596193748942848
339 if (RealRTCPeerConnection.prototype.setConfiguration)
340 {
341 let realSetConfiguration = Function.prototype.call.bind(
342 RealRTCPeerConnection.prototype.setConfiguration
343 );
344
345 RealRTCPeerConnection.prototype.setConfiguration = function(configuration)
346 {
347 configuration = protectConfiguration(configuration);
348
349 // Call the real method first, so that validates the configuration for
350 // us. Also we might as well since checkRequest is asynchronous anyway.
351 realSetConfiguration(this, configuration);
352 checkConfiguration(this, configuration);
353 };
354 }
355
356 function WrappedRTCPeerConnection(...args)
357 {
358 if (!(this instanceof WrappedRTCPeerConnection))
359 return WrappedRTCPeerConnection();
360
361 let configuration = protectConfiguration(args[0]);
362
363 // Since the old webkitRTCPeerConnection constructor takes an optional
364 // second argument we need to take care to pass that through. Necessary
365 // for older versions of Chrome such as 49.
366 let constraints = undefined;
367 if (args.length > 1)
368 constraints = args[1];
369
370 let peerconnection = new RealRTCPeerConnection(configuration, constraints);
371 checkConfiguration(peerconnection, configuration);
372 return peerconnection;
373 }
374
375 WrappedRTCPeerConnection.prototype = RealRTCPeerConnection.prototype;
376
377 let boundWrappedRTCPeerConnection = WrappedRTCPeerConnection.bind();
378 copyProperties(RealRTCPeerConnection, boundWrappedRTCPeerConnection,
379 ["generateCertificate", "name", "prototype"]);
380 RealRTCPeerConnection.prototype.constructor = boundWrappedRTCPeerConnection;
381
382 if ("RTCPeerConnection" in window)
383 window.RTCPeerConnection = boundWrappedRTCPeerConnection;
384 if ("webkitRTCPeerConnection" in window)
385 window.webkitRTCPeerConnection = boundWrappedRTCPeerConnection;
386 }
387
388 if (document instanceof HTMLDocument)
389 {
390 let script = document.createElement("script");
391 script.type = "application/javascript";
392 script.async = false;
393 script.textContent = "(" + injected + ")('" + randomEventName + "');";
394 document.documentElement.appendChild(script);
395 document.documentElement.removeChild(script);
396 }
OLDNEW
« no previous file with comments | « include.preload.js ('k') | lib/compat.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld