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

Delta Between Two Patch Sets: include.preload.js

Issue 29370970: [adblockpluschrome] Issue 3596 - Added support for CSS property filters to devtools panel (Closed)
Left Patch Set: Created Jan. 10, 2017, 10:54 a.m.
Right Patch Set: Addressed review comment Created Feb. 23, 2017, 2:23 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « dependencies ('k') | lib/devtools.js » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
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
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details. 12 * GNU General Public License for more details.
13 * 13 *
14 * You should have received a copy of the GNU General Public License 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/>. 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
16 */ 16 */
17 17
18 var typeMap = { 18 "use strict";
19
20 const typeMap = {
19 "img": "IMAGE", 21 "img": "IMAGE",
20 "input": "IMAGE", 22 "input": "IMAGE",
21 "picture": "IMAGE", 23 "picture": "IMAGE",
22 "audio": "MEDIA", 24 "audio": "MEDIA",
23 "video": "MEDIA", 25 "video": "MEDIA",
24 "frame": "SUBDOCUMENT", 26 "frame": "SUBDOCUMENT",
25 "iframe": "SUBDOCUMENT", 27 "iframe": "SUBDOCUMENT",
26 "object": "OBJECT", 28 "object": "OBJECT",
27 "embed": "OBJECT" 29 "embed": "OBJECT"
28 }; 30 };
29 31
30 function getURLsFromObjectElement(element) 32 function getURLsFromObjectElement(element)
31 { 33 {
32 var url = element.getAttribute("data"); 34 let url = element.getAttribute("data");
33 if (url) 35 if (url)
34 return [url]; 36 return [url];
35 37
36 for (var i = 0; i < element.children.length; i++) 38 for (let child of element.children)
37 { 39 {
38 var child = element.children[i];
39 if (child.localName != "param") 40 if (child.localName != "param")
40 continue; 41 continue;
41 42
42 var name = child.getAttribute("name"); 43 let name = child.getAttribute("name");
43 if (name != "movie" && // Adobe Flash 44 if (name != "movie" && // Adobe Flash
44 name != "source" && // Silverlight 45 name != "source" && // Silverlight
45 name != "src" && // Real Media + Quicktime 46 name != "src" && // Real Media + Quicktime
46 name != "FileName") // Windows Media 47 name != "FileName") // Windows Media
47 continue; 48 continue;
48 49
49 var value = child.getAttribute("value"); 50 let value = child.getAttribute("value");
50 if (!value) 51 if (!value)
51 continue; 52 continue;
52 53
53 return [value]; 54 return [value];
54 } 55 }
55 56
56 return []; 57 return [];
57 } 58 }
58 59
59 function getURLsFromAttributes(element) 60 function getURLsFromAttributes(element)
60 { 61 {
61 var urls = []; 62 let urls = [];
62 63
63 if (element.src) 64 if (element.src)
64 urls.push(element.src); 65 urls.push(element.src);
65 66
66 if (element.srcset) 67 if (element.srcset)
67 { 68 {
68 var candidates = element.srcset.split(","); 69 for (let candidate of element.srcset.split(","))
69 for (var i = 0; i < candidates.length; i++) 70 {
70 { 71 let url = candidate.trim().replace(/\s+\S+$/, "");
71 var url = candidates[i].trim().replace(/\s+\S+$/, "");
72 if (url) 72 if (url)
73 urls.push(url); 73 urls.push(url);
74 } 74 }
75 } 75 }
76 76
77 return urls; 77 return urls;
78 } 78 }
79 79
80 function getURLsFromMediaElement(element) 80 function getURLsFromMediaElement(element)
81 { 81 {
82 var urls = getURLsFromAttributes(element); 82 let urls = getURLsFromAttributes(element);
83 83
84 for (var i = 0; i < element.children.length; i++) 84 for (let child of element.children)
85 { 85 {
86 var child = element.children[i];
87 if (child.localName == "source" || child.localName == "track") 86 if (child.localName == "source" || child.localName == "track")
88 urls.push.apply(urls, getURLsFromAttributes(child)); 87 urls.push.apply(urls, getURLsFromAttributes(child));
89 } 88 }
90 89
91 if (element.poster) 90 if (element.poster)
92 urls.push(element.poster); 91 urls.push(element.poster);
93 92
94 return urls; 93 return urls;
95 } 94 }
96 95
97 function getURLsFromElement(element) 96 function getURLsFromElement(element)
98 { 97 {
99 var urls; 98 let urls;
100 switch (element.localName) 99 switch (element.localName)
101 { 100 {
102 case "object": 101 case "object":
103 urls = getURLsFromObjectElement(element); 102 urls = getURLsFromObjectElement(element);
104 break; 103 break;
105 104
106 case "video": 105 case "video":
107 case "audio": 106 case "audio":
108 case "picture": 107 case "picture":
109 urls = getURLsFromMediaElement(element); 108 urls = getURLsFromMediaElement(element);
110 break; 109 break;
111 110
112 default: 111 default:
113 urls = getURLsFromAttributes(element); 112 urls = getURLsFromAttributes(element);
114 break; 113 break;
115 } 114 }
116 115
117 for (var i = 0; i < urls.length; i++) 116 for (let i = 0; i < urls.length; i++)
118 { 117 {
119 if (/^(?!https?:)[\w-]+:/i.test(urls[i])) 118 if (/^(?!https?:)[\w-]+:/i.test(urls[i]))
120 urls.splice(i--, 1); 119 urls.splice(i--, 1);
121 } 120 }
122 121
123 return urls; 122 return urls;
124 } 123 }
125 124
126 function checkCollapse(element) 125 function checkCollapse(element)
127 { 126 {
128 var mediatype = typeMap[element.localName]; 127 let mediatype = typeMap[element.localName];
129 if (!mediatype) 128 if (!mediatype)
130 return; 129 return;
131 130
132 var urls = getURLsFromElement(element); 131 let urls = getURLsFromElement(element);
133 if (urls.length == 0) 132 if (urls.length == 0)
134 return; 133 return;
135 134
136 ext.backgroundPage.sendMessage( 135 ext.backgroundPage.sendMessage(
137 { 136 {
138 type: "filters.collapse", 137 type: "filters.collapse",
139 urls: urls, 138 urls: urls,
140 mediatype: mediatype, 139 mediatype: mediatype,
141 baseURL: document.location.href 140 baseURL: document.location.href
142 }, 141 },
143 142
144 function(collapse) 143 collapse =>
145 { 144 {
146 function collapseElement() 145 function collapseElement()
147 { 146 {
148 var propertyName = "display"; 147 let propertyName = "display";
149 var propertyValue = "none"; 148 let propertyValue = "none";
150 if (element.localName == "frame") 149 if (element.localName == "frame")
151 { 150 {
152 propertyName = "visibility"; 151 propertyName = "visibility";
153 propertyValue = "hidden"; 152 propertyValue = "hidden";
154 } 153 }
155 154
156 if (element.style.getPropertyValue(propertyName) != propertyValue || 155 if (element.style.getPropertyValue(propertyName) != propertyValue ||
157 element.style.getPropertyPriority(propertyName) != "important") 156 element.style.getPropertyPriority(propertyName) != "important")
158 element.style.setProperty(propertyName, propertyValue, "important"); 157 element.style.setProperty(propertyName, propertyValue, "important");
159 } 158 }
160 159
161 if (collapse) 160 if (collapse)
162 { 161 {
163 collapseElement(); 162 collapseElement();
164 163
165 new MutationObserver(collapseElement).observe( 164 new MutationObserver(collapseElement).observe(
166 element, { 165 element, {
167 attributes: true, 166 attributes: true,
168 attributeFilter: ["style"] 167 attributeFilter: ["style"]
169 } 168 }
170 ); 169 );
171 } 170 }
172 } 171 }
173 ); 172 );
174 } 173 }
175 174
176 function checkSitekey() 175 function checkSitekey()
177 { 176 {
178 var attr = document.documentElement.getAttribute("data-adblockkey"); 177 let attr = document.documentElement.getAttribute("data-adblockkey");
179 if (attr) 178 if (attr)
180 ext.backgroundPage.sendMessage({type: "filters.addKey", token: attr}); 179 ext.backgroundPage.sendMessage({type: "filters.addKey", token: attr});
181 } 180 }
182 181
183 function getContentDocument(element) 182 function getContentDocument(element)
184 { 183 {
185 try 184 try
186 { 185 {
187 return element.contentDocument; 186 return element.contentDocument;
188 } 187 }
189 catch (e) 188 catch (e)
190 { 189 {
191 return null; 190 return null;
192 } 191 }
193 } 192 }
194 193
195 function ElementHidingTracer(selectors) 194 function ElementHidingTracer()
196 { 195 {
197 this.selectors = selectors || []; 196 this.selectors = [];
Sebastian Noack 2017/01/11 16:20:40 It seems you removed the only code that initialize
wspee 2017/01/12 13:39:58 No longer relevant.
wspee 2017/01/19 16:20:14 Done.
197 this.filters = [];
198 198
199 this.changedNodes = []; 199 this.changedNodes = [];
200 this.timeout = null; 200 this.timeout = null;
201 201
202 this.observer = new MutationObserver(this.observe.bind(this)); 202 this.observer = new MutationObserver(this.observe.bind(this));
203 this.trace = this.trace.bind(this); 203 this.trace = this.trace.bind(this);
204 204
205 if (document.readyState == "loading") 205 if (document.readyState == "loading")
206 document.addEventListener("DOMContentLoaded", this.trace); 206 document.addEventListener("DOMContentLoaded", this.trace);
207 else 207 else
208 this.trace(); 208 this.trace();
209 } 209 }
210 ElementHidingTracer.prototype = { 210 ElementHidingTracer.prototype = {
211 checkNodes: function(nodes) 211 addSelectors(selectors, filters)
212 { 212 {
213 var matchedSelectors = []; 213 if (document.readyState != "loading")
214 214 this.checkNodes([document], selectors, filters);
215 // Find all selectors that match any hidden element inside the given nodes. 215
216 for (var i = 0; i < this.selectors.length; i++) 216 this.selectors.push(...selectors);
217 { 217 this.filters.push(...filters);
218 var selector = this.selectors[i]; 218 },
219 219
220 for (var j = 0; j < nodes.length; j++) 220 checkNodes(nodes, selectors, filters)
221 { 221 {
222 var elements = nodes[j].querySelectorAll(selector.selector); 222 let matchedSelectors = [];
223 var matched = false; 223
224 224 for (let i = 0; i < selectors.length; i++)
225 for (var k = 0; k < elements.length; k++) 225 {
226 nodes: for (let node of nodes)
227 {
228 let elements = node.querySelectorAll(selectors[i]);
229
230 for (let element of elements)
226 { 231 {
227 // Only consider selectors that actually have an effect on the 232 // Only consider selectors that actually have an effect on the
228 // computed styles, and aren't overridden by rules with higher 233 // computed styles, and aren't overridden by rules with higher
229 // priority, or haven't been circumvented in a different way. 234 // priority, or haven't been circumvented in a different way.
230 if (getComputedStyle(elements[k]).display == "none") 235 if (getComputedStyle(element).display == "none")
231 { 236 {
232 if (selector.filter) 237 matchedSelectors.push(filters[i].replace(/^.*?##/, ""));
233 matchedSelectors.push(selector.filter.replace(/^.*?##/, "")); 238 break nodes;
234 else
235 matchedSelectors.push(selector.selector);
236 matched = true;
237 break;
238 } 239 }
239 } 240 }
240
241 if (matched)
242 break;
243 } 241 }
244 } 242 }
245 243
246 if (matchedSelectors.length > 0) 244 if (matchedSelectors.length > 0)
247 ext.backgroundPage.sendMessage({ 245 ext.backgroundPage.sendMessage({
248 type: "devtools.traceElemHide", 246 type: "devtools.traceElemHide",
249 selectors: matchedSelectors 247 selectors: matchedSelectors
250 }); 248 });
251 }, 249 },
252 250
253 onTimeout: function() 251 onTimeout()
254 { 252 {
255 this.checkNodes(this.changedNodes); 253 this.checkNodes(this.changedNodes, this.selectors, this.filters);
256 this.changedNodes = []; 254 this.changedNodes = [];
257 this.timeout = null; 255 this.timeout = null;
258 }, 256 },
259 257
260 observe: function(mutations) 258 observe(mutations)
261 { 259 {
262 // Forget previously changed nodes that are no longer in the DOM. 260 // Forget previously changed nodes that are no longer in the DOM.
263 for (var i = 0; i < this.changedNodes.length; i++) 261 for (let i = 0; i < this.changedNodes.length; i++)
264 { 262 {
265 if (!document.contains(this.changedNodes[i])) 263 if (!document.contains(this.changedNodes[i]))
266 this.changedNodes.splice(i--, 1); 264 this.changedNodes.splice(i--, 1);
267 } 265 }
268 266
269 for (var j = 0; j < mutations.length; j++) 267 for (let mutation of mutations)
270 { 268 {
271 var mutation = mutations[j]; 269 let node = mutation.target;
272 var node = mutation.target;
273 270
274 // Ignore mutations of nodes that aren't in the DOM anymore. 271 // Ignore mutations of nodes that aren't in the DOM anymore.
275 if (!document.contains(node)) 272 if (!document.contains(node))
276 continue; 273 continue;
277 274
278 // Since querySelectorAll() doesn't consider the root itself 275 // Since querySelectorAll() doesn't consider the root itself
279 // and since CSS selectors can also match siblings, we have 276 // and since CSS selectors can also match siblings, we have
280 // to consider the parent node for attribute mutations. 277 // to consider the parent node for attribute mutations.
281 if (mutation.type == "attributes") 278 if (mutation.type == "attributes")
282 node = node.parentNode; 279 node = node.parentNode;
283 280
284 var addNode = true; 281 let addNode = true;
285 for (var k = 0; k < this.changedNodes.length; k++) 282 for (let i = 0; i < this.changedNodes.length; i++)
286 { 283 {
287 var previouslyChangedNode = this.changedNodes[k]; 284 let previouslyChangedNode = this.changedNodes[i];
288 285
289 // If we are already going to check an ancestor of this node, 286 // If we are already going to check an ancestor of this node,
290 // we can ignore this node, since it will be considered anyway 287 // we can ignore this node, since it will be considered anyway
291 // when checking one of its ancestors. 288 // when checking one of its ancestors.
292 if (previouslyChangedNode.contains(node)) 289 if (previouslyChangedNode.contains(node))
293 { 290 {
294 addNode = false; 291 addNode = false;
295 break; 292 break;
296 } 293 }
297 294
298 // If this node is an ancestor of a node that previously changed, 295 // If this node is an ancestor of a node that previously changed,
299 // we can ignore that node, since it will be considered anyway 296 // we can ignore that node, since it will be considered anyway
300 // when checking one of its ancestors. 297 // when checking one of its ancestors.
301 if (node.contains(previouslyChangedNode)) 298 if (node.contains(previouslyChangedNode))
302 this.changedNodes.splice(k--, 1); 299 this.changedNodes.splice(i--, 1);
303 } 300 }
304 301
305 if (addNode) 302 if (addNode)
306 this.changedNodes.push(node); 303 this.changedNodes.push(node);
307 } 304 }
308 305
309 // Check only nodes whose descendants have changed, and not more often 306 // Check only nodes whose descendants have changed, and not more often
310 // than once a second. Otherwise large pages with a lot of DOM mutations 307 // than once a second. Otherwise large pages with a lot of DOM mutations
311 // (like YouTube) freeze when the devtools panel is active. 308 // (like YouTube) freeze when the devtools panel is active.
312 if (this.timeout == null) 309 if (this.timeout == null)
313 this.timeout = setTimeout(this.onTimeout.bind(this), 1000); 310 this.timeout = setTimeout(this.onTimeout.bind(this), 1000);
314 }, 311 },
315 312
316 trace: function() 313 trace()
317 { 314 {
318 this.checkNodes([document]); 315 this.checkNodes([document], this.selectors, this.filters);
319 316
320 this.observer.observe( 317 this.observer.observe(
321 document, 318 document,
322 { 319 {
323 childList: true, 320 childList: true,
324 attributes: true, 321 attributes: true,
325 subtree: true 322 subtree: true
326 } 323 }
327 ); 324 );
328 }, 325 },
329 326
330 disconnect: function() 327 disconnect()
331 { 328 {
332 document.removeEventListener("DOMContentLoaded", this.trace); 329 document.removeEventListener("DOMContentLoaded", this.trace);
333 this.observer.disconnect(); 330 this.observer.disconnect();
334 clearTimeout(this.timeout); 331 clearTimeout(this.timeout);
335 } 332 }
336 }; 333 };
337 334
338 function runInPageContext(fn, arg) 335 function runInPageContext(fn, arg)
339 { 336 {
340 var script = document.createElement("script"); 337 let script = document.createElement("script");
341 script.type = "application/javascript"; 338 script.type = "application/javascript";
342 script.async = false; 339 script.async = false;
343 script.textContent = "(" + fn + ")(" + JSON.stringify(arg) + ");"; 340 script.textContent = "(" + fn + ")(" + JSON.stringify(arg) + ");";
344 document.documentElement.appendChild(script); 341 document.documentElement.appendChild(script);
345 document.documentElement.removeChild(script); 342 document.documentElement.removeChild(script);
346 } 343 }
347 344
348 // Chrome doesn't allow us to intercept WebSockets[1], and therefore 345 // Chrome doesn't allow us to intercept WebSockets[1], and therefore
349 // some ad networks are misusing them as a way to serve adverts and circumvent 346 // some ad networks are misusing them as a way to serve adverts and circumvent
350 // us. As a workaround we wrap WebSocket, preventing blocked WebSocket 347 // us. As a workaround we wrap WebSocket, preventing blocked WebSocket
351 // connections from being opened. 348 // connections from being opened.
352 // [1] - https://bugs.chromium.org/p/chromium/issues/detail?id=129353 349 // [1] - https://bugs.chromium.org/p/chromium/issues/detail?id=129353
353 function wrapWebSocket() 350 function wrapWebSocket()
354 { 351 {
355 var eventName = "abpws-" + Math.random().toString(36).substr(2); 352 let eventName = "abpws-" + Math.random().toString(36).substr(2);
356 353
357 document.addEventListener(eventName, function(event) 354 document.addEventListener(eventName, event =>
358 { 355 {
359 ext.backgroundPage.sendMessage({ 356 ext.backgroundPage.sendMessage({
360 type: "request.websocket", 357 type: "request.websocket",
361 url: event.detail.url 358 url: event.detail.url
362 }, function (block) 359 }, block =>
363 { 360 {
364 document.dispatchEvent( 361 document.dispatchEvent(
365 new CustomEvent(eventName + "-" + event.detail.url, {detail: block}) 362 new CustomEvent(eventName + "-" + event.detail.url, {detail: block})
366 ); 363 );
367 }); 364 });
368 }); 365 });
369 366
370 runInPageContext(function(eventName) 367 runInPageContext(eventName =>
371 { 368 {
372 // As far as possible we must track everything we use that could be 369 // As far as possible we must track everything we use that could be
373 // sabotaged by the website later in order to circumvent us. 370 // sabotaged by the website later in order to circumvent us.
374 var RealWebSocket = WebSocket; 371 let RealWebSocket = WebSocket;
375 var closeWebSocket = Function.prototype.call.bind(RealWebSocket.prototype.cl ose); 372 let closeWebSocket = Function.prototype.call.bind(RealWebSocket.prototype.cl ose);
376 var addEventListener = document.addEventListener.bind(document); 373 let addEventListener = document.addEventListener.bind(document);
377 var removeEventListener = document.removeEventListener.bind(document); 374 let removeEventListener = document.removeEventListener.bind(document);
378 var dispatchEvent = document.dispatchEvent.bind(document); 375 let dispatchEvent = document.dispatchEvent.bind(document);
379 var CustomEvent = window.CustomEvent; 376 let CustomEvent = window.CustomEvent;
380 377
381 function checkRequest(url, callback) 378 function checkRequest(url, callback)
382 { 379 {
383 var incomingEventName = eventName + "-" + url; 380 let incomingEventName = eventName + "-" + url;
384 function listener(event) 381 function listener(event)
385 { 382 {
386 callback(event.detail); 383 callback(event.detail);
387 removeEventListener(incomingEventName, listener); 384 removeEventListener(incomingEventName, listener);
388 } 385 }
389 addEventListener(incomingEventName, listener); 386 addEventListener(incomingEventName, listener);
390 387
391 dispatchEvent(new CustomEvent(eventName, { 388 dispatchEvent(new CustomEvent(eventName, {
392 detail: {url: url} 389 detail: {url: url}
393 })); 390 }));
394 } 391 }
395 392
396 function WrappedWebSocket(url) 393 function WrappedWebSocket(url)
397 { 394 {
398 // Throw correct exceptions if the constructor is used improperly. 395 // Throw correct exceptions if the constructor is used improperly.
399 if (!(this instanceof WrappedWebSocket)) return RealWebSocket(); 396 if (!(this instanceof WrappedWebSocket)) return RealWebSocket();
400 if (arguments.length < 1) return new RealWebSocket(); 397 if (arguments.length < 1) return new RealWebSocket();
401 398
402 var websocket; 399 let websocket;
403 if (arguments.length == 1) 400 if (arguments.length == 1)
404 websocket = new RealWebSocket(url); 401 websocket = new RealWebSocket(url);
405 else 402 else
406 websocket = new RealWebSocket(url, arguments[1]); 403 websocket = new RealWebSocket(url, arguments[1]);
407 404
408 checkRequest(websocket.url, function(blocked) 405 checkRequest(websocket.url, blocked =>
409 { 406 {
410 if (blocked) 407 if (blocked)
411 closeWebSocket(websocket); 408 closeWebSocket(websocket);
412 }); 409 });
413 410
414 return websocket; 411 return websocket;
415 } 412 }
416 WrappedWebSocket.prototype = RealWebSocket.prototype; 413 WrappedWebSocket.prototype = RealWebSocket.prototype;
417 WebSocket = WrappedWebSocket.bind(); 414 WebSocket = WrappedWebSocket.bind();
418 Object.defineProperties(WebSocket, { 415 Object.defineProperties(WebSocket, {
419 CONNECTING: {value: RealWebSocket.CONNECTING, enumerable: true}, 416 CONNECTING: {value: RealWebSocket.CONNECTING, enumerable: true},
420 OPEN: {value: RealWebSocket.OPEN, enumerable: true}, 417 OPEN: {value: RealWebSocket.OPEN, enumerable: true},
421 CLOSING: {value: RealWebSocket.CLOSING, enumerable: true}, 418 CLOSING: {value: RealWebSocket.CLOSING, enumerable: true},
422 CLOSED: {value: RealWebSocket.CLOSED, enumerable: true}, 419 CLOSED: {value: RealWebSocket.CLOSED, enumerable: true},
423 prototype: {value: RealWebSocket.prototype} 420 prototype: {value: RealWebSocket.prototype}
424 }); 421 });
425 422
426 RealWebSocket.prototype.constructor = WebSocket; 423 RealWebSocket.prototype.constructor = WebSocket;
427 }, eventName); 424 }, eventName);
428 } 425 }
429 426
430 function ElemHide() 427 function ElemHide()
431 { 428 {
432 this.shadow = this.createShadowTree(); 429 this.shadow = this.createShadowTree();
433 this.style = null; 430 this.style = null;
434 this.tracer = null; 431 this.tracer = null;
435 432
436 this.elemHideEmulation = new ElemHideEmulation( 433 this.elemHideEmulation = new ElemHideEmulation(
437 window, 434 window,
438 function(callback) 435 callback =>
439 { 436 {
440 ext.backgroundPage.sendMessage({ 437 ext.backgroundPage.sendMessage({
441 type: "filters.get", 438 type: "filters.get",
442 what: "elemhideemulation" 439 what: "elemhideemulation"
443 }, callback); 440 }, callback);
444 }, 441 },
445 this.addSelectors.bind(this) 442 this.addSelectors.bind(this)
446 ); 443 );
447 } 444 }
448 ElemHide.prototype = { 445 ElemHide.prototype = {
449 selectorGroupSize: 200, 446 selectorGroupSize: 200,
450 447
451 createShadowTree: function() 448 createShadowTree()
452 { 449 {
453 // Use Shadow DOM if available as to not mess with with web pages that 450 // Use Shadow DOM if available as to not mess with with web pages that
454 // rely on the order of their own <style> tags (#309). However, creating 451 // rely on the order of their own <style> tags (#309). However, creating
455 // a shadow root breaks running CSS transitions. So we have to create 452 // a shadow root breaks running CSS transitions. So we have to create
456 // the shadow root before transistions might start (#452). 453 // the shadow root before transistions might start (#452).
457 if (!("createShadowRoot" in document.documentElement)) 454 if (!("createShadowRoot" in document.documentElement))
458 return null; 455 return null;
459 456
460 // Using shadow DOM causes issues on some Google websites, 457 // Using shadow DOM causes issues on some Google websites,
461 // including Google Docs, Gmail and Blogger (#1770, #2602, #2687). 458 // including Google Docs, Gmail and Blogger (#1770, #2602, #2687).
462 if (/\.(?:google|blogger)\.com$/.test(document.domain)) 459 if (/\.(?:google|blogger)\.com$/.test(document.domain))
463 return null; 460 return null;
464 461
465 // Finally since some users have both AdBlock and Adblock Plus installed we 462 // Finally since some users have both AdBlock and Adblock Plus installed we
466 // have to consider how the two extensions interact. For example we want to 463 // have to consider how the two extensions interact. For example we want to
467 // avoid creating the shadowRoot twice. 464 // avoid creating the shadowRoot twice.
468 var shadow = document.documentElement.shadowRoot || 465 let shadow = document.documentElement.shadowRoot ||
469 document.documentElement.createShadowRoot(); 466 document.documentElement.createShadowRoot();
470 shadow.appendChild(document.createElement("shadow")); 467 shadow.appendChild(document.createElement("shadow"));
471 468
472 // Stop the website from messing with our shadow root (#4191, #4298). 469 // Stop the website from messing with our shadow root (#4191, #4298).
473 if ("shadowRoot" in Element.prototype) 470 if ("shadowRoot" in Element.prototype)
474 { 471 {
475 runInPageContext(function() 472 runInPageContext(() =>
476 { 473 {
477 var ourShadowRoot = document.documentElement.shadowRoot; 474 let ourShadowRoot = document.documentElement.shadowRoot;
478 if (!ourShadowRoot) 475 if (!ourShadowRoot)
479 return; 476 return;
480 var desc = Object.getOwnPropertyDescriptor(Element.prototype, "shadowRoo t"); 477 let desc = Object.getOwnPropertyDescriptor(Element.prototype, "shadowRoo t");
481 var shadowRoot = Function.prototype.call.bind(desc.get); 478 let shadowRoot = Function.prototype.call.bind(desc.get);
482 479
483 Object.defineProperty(Element.prototype, "shadowRoot", { 480 Object.defineProperty(Element.prototype, "shadowRoot", {
484 configurable: true, enumerable: true, get: function() 481 configurable: true, enumerable: true, get()
485 { 482 {
486 var shadow = shadowRoot(this); 483 let shadow = shadowRoot(this);
487 return shadow == ourShadowRoot ? null : shadow; 484 return shadow == ourShadowRoot ? null : shadow;
488 } 485 }
489 }); 486 });
490 }, null); 487 }, null);
491 } 488 }
492 489
493 return shadow; 490 return shadow;
494 }, 491 },
495 492
496 addSelectors: function(selectors, filters) 493 addSelectors(selectors, filters)
497 { 494 {
498 if (selectors.length == 0) 495 if (selectors.length == 0)
499 return; 496 return;
500
501 if (this.tracer != null)
502 {
503 // The selector is beeing used to identify the filter that is responsible
504 // for hiding a particular element in the devtools panel. The
505 // ElementHidingTracer uses the selector to figure out if a filter is
506 // used on the current document. In case of an ElemHideEmulationFilter
507 // the selector doesn't match the selector of the filter
508 // ([-abp-properties=...] vs actual css selector depending on the sites
509 // css). The To still allow the devtools panel to find the correct filter
510 // we pass both the selector and the filter string to the
511 // ElementHidingTracer.
512 for (var i = 0; i < selectors.length; i++)
513 {
514 this.tracer.selectors.push({
Sebastian Noack 2017/01/11 16:20:40 Is it guaranteed that the order of selectors match
Sebastian Noack 2017/01/11 16:20:40 Creating a new object for each selector might pote
wspee 2017/01/12 13:39:58 Indeed ... if you look at findSelectors there can
wspee 2017/01/12 13:39:58 No longer relevant.
wspee 2017/01/19 16:20:14 According to the chrome profiler an array with 100
wspee 2017/01/19 16:20:14 Done.
515 selector: selectors[i],
516 filter: filters ? filters[i] : null
517 });
518 }
519 }
520 497
521 if (!this.style) 498 if (!this.style)
522 { 499 {
523 // Create <style> element lazily, only if we add styles. Add it to 500 // Create <style> element lazily, only if we add styles. Add it to
524 // the shadow DOM if possible. Otherwise fallback to the <head> or 501 // the shadow DOM if possible. Otherwise fallback to the <head> or
525 // <html> element. If we have injected a style element before that 502 // <html> element. If we have injected a style element before that
526 // has been removed (the sheet property is null), create a new one. 503 // has been removed (the sheet property is null), create a new one.
527 this.style = document.createElement("style"); 504 this.style = document.createElement("style");
528 (this.shadow || document.head 505 (this.shadow || document.head
529 || document.documentElement).appendChild(this.style); 506 || document.documentElement).appendChild(this.style);
530 507
531 // It can happen that the frame already navigated to a different 508 // It can happen that the frame already navigated to a different
532 // document while we were waiting for the background page to respond. 509 // document while we were waiting for the background page to respond.
533 // In that case the sheet property will stay null, after addind the 510 // In that case the sheet property will stay null, after addind the
534 // <style> element to the shadow DOM. 511 // <style> element to the shadow DOM.
535 if (!this.style.sheet) 512 if (!this.style.sheet)
536 return; 513 return;
537 } 514 }
538 515
539 // If using shadow DOM, we have to add the ::content pseudo-element 516 // If using shadow DOM, we have to add the ::content pseudo-element
540 // before each selector, in order to match elements within the 517 // before each selector, in order to match elements within the
541 // insertion point. 518 // insertion point.
519 let preparedSelectors = [];
542 if (this.shadow) 520 if (this.shadow)
543 { 521 {
544 var preparedSelectors = []; 522 for (let selector of selectors)
545 for (var i = 0; i < selectors.length; i++) 523 {
546 { 524 let subSelectors = splitSelector(selector);
547 var subSelectors = splitSelector(selectors[i]); 525 for (let subSelector of subSelectors)
548 for (var j = 0; j < subSelectors.length; j++) 526 preparedSelectors.push("::content " + subSelector);
549 preparedSelectors.push("::content " + subSelectors[j]); 527 }
550 } 528 }
551 selectors = preparedSelectors; 529 else
530 {
531 preparedSelectors = selectors;
552 } 532 }
553 533
554 // Safari only allows 8192 primitive selectors to be injected at once[1], we 534 // Safari only allows 8192 primitive selectors to be injected at once[1], we
555 // therefore chunk the inserted selectors into groups of 200 to be safe. 535 // therefore chunk the inserted selectors into groups of 200 to be safe.
556 // (Chrome also has a limit, larger... but we're not certain exactly what it 536 // (Chrome also has a limit, larger... but we're not certain exactly what it
557 // is! Edge apparently has no such limit.) 537 // is! Edge apparently has no such limit.)
558 // [1] - https://github.com/WebKit/webkit/blob/1cb2227f6b2a1035f7bdc46e5ab69 debb75fc1de/Source/WebCore/css/RuleSet.h#L68 538 // [1] - https://github.com/WebKit/webkit/blob/1cb2227f6b2a1035f7bdc46e5ab69 debb75fc1de/Source/WebCore/css/RuleSet.h#L68
559 for (var i = 0; i < selectors.length; i += this.selectorGroupSize) 539 for (let i = 0; i < preparedSelectors.length; i += this.selectorGroupSize)
560 { 540 {
561 var selector = selectors.slice(i, i + this.selectorGroupSize).join(", "); 541 let selector = preparedSelectors.slice(i, i + this.selectorGroupSize).join (", ");
562 this.style.sheet.insertRule(selector + "{display: none !important;}", 542 this.style.sheet.insertRule(selector + "{display: none !important;}",
563 this.style.sheet.cssRules.length); 543 this.style.sheet.cssRules.length);
564 } 544 }
565 }, 545
566 546 if (this.tracer)
567 apply: function() 547 this.tracer.addSelectors(selectors, filters || selectors);
568 { 548 },
569 var selectors = null; 549
570 550 apply()
571 var checkLoaded = function() 551 {
552 ext.backgroundPage.sendMessage({type: "get-selectors"}, response =>
572 { 553 {
573 if (this.tracer) 554 if (this.tracer)
574 this.tracer.disconnect(); 555 this.tracer.disconnect();
575 this.tracer = null; 556 this.tracer = null;
576 557
577 if (selectors.trace)
578 this.tracer = new ElementHidingTracer();
579
580 if (this.style && this.style.parentElement) 558 if (this.style && this.style.parentElement)
581 this.style.parentElement.removeChild(this.style); 559 this.style.parentElement.removeChild(this.style);
582 this.style = null; 560 this.style = null;
583 561
584 if (selectors.selectors) 562 if (response.trace)
585 this.addSelectors(selectors.selectors); 563 this.tracer = new ElementHidingTracer();
564
565 this.addSelectors(response.selectors);
586 this.elemHideEmulation.apply(); 566 this.elemHideEmulation.apply();
587
588 if (this.tracer && this.tracer.selectors.length)
Sebastian Noack 2017/01/11 16:20:40 Any particular reason (except for a hypothetical m
wspee 2017/01/12 13:39:58 No longer relevant.
wspee 2017/01/19 16:20:15 Done.
589 this.tracer.checkNodes([document]);
590 }.bind(this);
591
592 ext.backgroundPage.sendMessage({type: "get-selectors"}, (response) =>
Sebastian Noack 2017/01/11 16:20:40 The parentheses around the "response" argument are
wspee 2017/01/12 13:39:58 No longer relevant.
wspee 2017/01/19 16:20:14 Done.
593 {
594 selectors = response;
595 this.elemHideEmulation.load(() => { checkLoaded(); });
Sebastian Noack 2017/01/11 16:20:40 The arrow function seems redundant. Why not just p
wspee 2017/01/12 13:39:58 No longer relevant.
wspee 2017/01/19 16:20:14 Done.
596 }); 567 });
597 } 568 }
598 }; 569 };
599 570
600 if (document instanceof HTMLDocument) 571 if (document instanceof HTMLDocument)
601 { 572 {
602 checkSitekey(); 573 checkSitekey();
603 wrapWebSocket(); 574 wrapWebSocket();
604 575
576 // This variable is also used by our other content scripts, outside of the
577 // current scope.
605 var elemhide = new ElemHide(); 578 var elemhide = new ElemHide();
606 elemhide.apply(); 579 elemhide.apply();
607 580
608 document.addEventListener("error", function(event) 581 document.addEventListener("error", event =>
609 { 582 {
610 checkCollapse(event.target); 583 checkCollapse(event.target);
611 }, true); 584 }, true);
612 585
613 document.addEventListener("load", function(event) 586 document.addEventListener("load", event =>
614 { 587 {
615 var element = event.target; 588 let element = event.target;
616 if (/^i?frame$/.test(element.localName)) 589 if (/^i?frame$/.test(element.localName))
617 checkCollapse(element); 590 checkCollapse(element);
618 }, true); 591 }, true);
619 } 592 }
LEFTRIGHT

Powered by Google App Engine
This is Rietveld