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

Delta Between Two Patch Sets: include.preload.js

Issue 29374674: Issue 4864 - Start using ESLint for adblockpluschrome (Closed)
Left Patch Set: Use var for ext declarations again Created Feb. 8, 2017, 9:02 a.m.
Right Patch Set: Use .includes again Created March 31, 2017, 8:37 a.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 | « ext/common.js ('k') | lib/compat.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-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
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 /* globals ElemHideEmulation, splitSelector */ 18 /* globals ElemHideEmulation, splitSelector */
19 19
20 "use strict"; 20 "use strict";
21 21
22 // This variable is also used by our other content scripts. 22 // This variable is also used by our other content scripts.
Sebastian Noack 2017/02/09 01:04:50 This comment is kinda redundant with the /* global
wspee 2017/03/02 10:27:13 You might want to also comment on this? But as far
kzar 2017/03/07 11:41:35 Yes, I think for now this stuff will stay as it is
23 let elemhide; 23 let elemhide;
24 24
25 const typeMap = new Map([ 25 const typeMap = new Map([
26 ["img", "IMAGE"], 26 ["img", "IMAGE"],
27 ["input", "IMAGE"], 27 ["input", "IMAGE"],
28 ["picture", "IMAGE"], 28 ["picture", "IMAGE"],
29 ["audio", "MEDIA"], 29 ["audio", "MEDIA"],
30 ["video", "MEDIA"], 30 ["video", "MEDIA"],
31 ["frame", "SUBDOCUMENT"], 31 ["frame", "SUBDOCUMENT"],
32 ["iframe", "SUBDOCUMENT"], 32 ["iframe", "SUBDOCUMENT"],
(...skipping 144 matching lines...) Expand 10 before | Expand all | Expand 10 after
177 ); 177 );
178 } 178 }
179 179
180 function checkSitekey() 180 function checkSitekey()
181 { 181 {
182 let attr = document.documentElement.getAttribute("data-adblockkey"); 182 let attr = document.documentElement.getAttribute("data-adblockkey");
183 if (attr) 183 if (attr)
184 ext.backgroundPage.sendMessage({type: "filters.addKey", token: attr}); 184 ext.backgroundPage.sendMessage({type: "filters.addKey", token: attr});
185 } 185 }
186 186
187 function getContentDocument(element) 187 function ElementHidingTracer()
188 { 188 {
189 try 189 this.selectors = [];
190 { 190 this.filters = [];
191 return element.contentDocument;
192 }
193 catch (e)
194 {
195 return null;
196 }
197 }
198
199 function ElementHidingTracer(selectors)
200 {
201 this.selectors = selectors;
202 191
203 this.changedNodes = []; 192 this.changedNodes = [];
204 this.timeout = null; 193 this.timeout = null;
205 194
206 this.observer = new MutationObserver(this.observe.bind(this)); 195 this.observer = new MutationObserver(this.observe.bind(this));
207 this.trace = this.trace.bind(this); 196 this.trace = this.trace.bind(this);
208 197
209 if (document.readyState == "loading") 198 if (document.readyState == "loading")
210 document.addEventListener("DOMContentLoaded", this.trace); 199 document.addEventListener("DOMContentLoaded", this.trace);
211 else 200 else
212 this.trace(); 201 this.trace();
213 } 202 }
214 ElementHidingTracer.prototype = { 203 ElementHidingTracer.prototype = {
215 checkNodes(nodes) 204 addSelectors(selectors, filters)
205 {
206 if (document.readyState != "loading")
207 this.checkNodes([document], selectors, filters);
208
209 this.selectors.push(...selectors);
210 this.filters.push(...filters);
211 },
212
213 checkNodes(nodes, selectors, filters)
216 { 214 {
217 let matchedSelectors = []; 215 let matchedSelectors = [];
218 216
219 // Find all selectors that match any hidden element inside the given nodes. 217 for (let i = 0; i < selectors.length; i++)
220 for (let selector of this.selectors) 218 {
221 { 219 nodes: for (let node of nodes)
222 for (let node of nodes) 220 {
223 { 221 let elements = node.querySelectorAll(selectors[i]);
224 let elements = node.querySelectorAll(selector);
225 let matched = false;
226 222
227 for (let element of elements) 223 for (let element of elements)
228 { 224 {
229 // Only consider selectors that actually have an effect on the 225 // Only consider selectors that actually have an effect on the
230 // computed styles, and aren't overridden by rules with higher 226 // computed styles, and aren't overridden by rules with higher
231 // priority, or haven't been circumvented in a different way. 227 // priority, or haven't been circumvented in a different way.
232 if (getComputedStyle(element).display == "none") 228 if (getComputedStyle(element).display == "none")
233 { 229 {
234 matchedSelectors.push(selector); 230 matchedSelectors.push(filters[i].replace(/^.*?##/, ""));
235 matched = true; 231 break nodes;
236 break;
237 } 232 }
238 } 233 }
239
240 if (matched)
241 break;
242 } 234 }
243 } 235 }
244 236
245 if (matchedSelectors.length > 0) 237 if (matchedSelectors.length > 0)
246 { 238 {
247 ext.backgroundPage.sendMessage({ 239 ext.backgroundPage.sendMessage({
248 type: "devtools.traceElemHide", 240 type: "devtools.traceElemHide",
249 selectors: matchedSelectors 241 selectors: matchedSelectors
250 }); 242 });
251 } 243 }
252 }, 244 },
253 245
254 onTimeout() 246 onTimeout()
255 { 247 {
256 this.checkNodes(this.changedNodes); 248 this.checkNodes(this.changedNodes, this.selectors, this.filters);
257 this.changedNodes = []; 249 this.changedNodes = [];
258 this.timeout = null; 250 this.timeout = null;
259 }, 251 },
260 252
261 observe(mutations) 253 observe(mutations)
262 { 254 {
263 // Forget previously changed nodes that are no longer in the DOM. 255 // Forget previously changed nodes that are no longer in the DOM.
264 for (let i = 0; i < this.changedNodes.length; i++) 256 for (let i = 0; i < this.changedNodes.length; i++)
265 { 257 {
266 if (!document.contains(this.changedNodes[i])) 258 if (!document.contains(this.changedNodes[i]))
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
308 300
309 // Check only nodes whose descendants have changed, and not more often 301 // 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 302 // than once a second. Otherwise large pages with a lot of DOM mutations
311 // (like YouTube) freeze when the devtools panel is active. 303 // (like YouTube) freeze when the devtools panel is active.
312 if (this.timeout == null) 304 if (this.timeout == null)
313 this.timeout = setTimeout(this.onTimeout.bind(this), 1000); 305 this.timeout = setTimeout(this.onTimeout.bind(this), 1000);
314 }, 306 },
315 307
316 trace() 308 trace()
317 { 309 {
318 this.checkNodes([document]); 310 this.checkNodes([document], this.selectors, this.filters);
319 311
320 this.observer.observe( 312 this.observer.observe(
321 document, 313 document,
322 { 314 {
323 childList: true, 315 childList: true,
324 attributes: true, 316 attributes: true,
325 subtree: true 317 subtree: true
326 } 318 }
327 ); 319 );
328 }, 320 },
329 321
330 disconnect() 322 disconnect()
331 { 323 {
332 document.removeEventListener("DOMContentLoaded", this.trace); 324 document.removeEventListener("DOMContentLoaded", this.trace);
333 this.observer.disconnect(); 325 this.observer.disconnect();
334 clearTimeout(this.timeout); 326 clearTimeout(this.timeout);
335 } 327 }
336 }; 328 };
337 329
338 function runInPageContext(fn, arg) 330 function runInPageContext(fn, arg)
339 { 331 {
340 let script = document.createElement("script"); 332 let script = document.createElement("script");
341 script.type = "application/javascript"; 333 script.type = "application/javascript";
342 script.async = false; 334 script.async = false;
343 script.textContent = "(" + fn + ")(" + JSON.stringify(arg) + ");"; 335 script.textContent = "(" + fn + ")(" + JSON.stringify(arg) + ");";
344 document.documentElement.appendChild(script); 336 document.documentElement.appendChild(script);
345 document.documentElement.removeChild(script); 337 document.documentElement.removeChild(script);
346 } 338 }
347 339
348 // Chrome doesn't allow us to intercept WebSockets[1], and therefore 340 // Before Chrome 58 the webRequest API didn't allow us to intercept
349 // some ad networks are misusing them as a way to serve adverts and circumvent 341 // WebSockets[1], and therefore some ad networks are misusing them as a way to
350 // us. As a workaround we wrap WebSocket, preventing blocked WebSocket 342 // serve adverts and circumvent us. As a workaround we wrap WebSocket,
351 // connections from being opened. 343 // preventing blocked WebSocket connections from being opened.
352 // [1] - https://bugs.chromium.org/p/chromium/issues/detail?id=129353 344 // [1] - https://bugs.chromium.org/p/chromium/issues/detail?id=129353
353 function wrapWebSocket() 345 function wrapWebSocket()
354 { 346 {
355 let randomEventName = "abpws-" + Math.random().toString(36).substr(2); 347 let randomEventName = "abpws-" + Math.random().toString(36).substr(2);
356 348
357 document.addEventListener(randomEventName, event => 349 document.addEventListener(randomEventName, event =>
358 { 350 {
359 ext.backgroundPage.sendMessage({ 351 ext.backgroundPage.sendMessage({
360 type: "request.websocket", 352 type: "request.websocket",
361 url: event.detail.url 353 url: event.detail.url
362 }, block => 354 }, block =>
363 { 355 {
364 document.dispatchEvent(new CustomEvent( 356 document.dispatchEvent(new CustomEvent(
365 randomEventName + "-" + event.detail.url, {detail: block} 357 randomEventName + "-" + event.detail.url, {detail: block}
Sebastian Noack 2017/02/09 01:04:50 If you rename the argument to "detail", you can us
kzar 2017/02/20 10:27:31 I think block is a better name for the variable th
366 )); 358 ));
367 }); 359 });
368 }); 360 });
369 361
370 runInPageContext(eventName => 362 runInPageContext(eventName =>
371 { 363 {
372 // As far as possible we must track everything we use that could be 364 // As far as possible we must track everything we use that could be
373 // sabotaged by the website later in order to circumvent us. 365 // sabotaged by the website later in order to circumvent us.
374 let RealWebSocket = WebSocket; 366 let RealWebSocket = WebSocket;
375 let RealCustomEvent = window.CustomEvent; 367 let RealCustomEvent = window.CustomEvent;
(...skipping 16 matching lines...) Expand all
392 384
393 dispatchEvent(new RealCustomEvent(eventName, {detail: {url}})); 385 dispatchEvent(new RealCustomEvent(eventName, {detail: {url}}));
394 } 386 }
395 387
396 function WrappedWebSocket(url, ...args) 388 function WrappedWebSocket(url, ...args)
397 { 389 {
398 // Throw correct exceptions if the constructor is used improperly. 390 // Throw correct exceptions if the constructor is used improperly.
399 if (!(this instanceof WrappedWebSocket)) return RealWebSocket(); 391 if (!(this instanceof WrappedWebSocket)) return RealWebSocket();
400 if (arguments.length < 1) return new RealWebSocket(); 392 if (arguments.length < 1) return new RealWebSocket();
401 393
402 let websocket; 394 let websocket = new RealWebSocket(url, ...args);
403 if (arguments.length == 1)
404 websocket = new RealWebSocket(url);
405 else
406 websocket = new RealWebSocket(url, args[0]);
407 395
408 checkRequest(websocket.url, blocked => 396 checkRequest(websocket.url, blocked =>
409 { 397 {
410 if (blocked) 398 if (blocked)
411 closeWebSocket(websocket); 399 closeWebSocket(websocket);
412 }); 400 });
413 401
414 return websocket; 402 return websocket;
415 } 403 }
416 WrappedWebSocket.prototype = RealWebSocket.prototype; 404 WrappedWebSocket.prototype = RealWebSocket.prototype;
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after
478 if (!ourShadowRoot) 466 if (!ourShadowRoot)
479 return; 467 return;
480 let desc = Object.getOwnPropertyDescriptor(Element.prototype, 468 let desc = Object.getOwnPropertyDescriptor(Element.prototype,
481 "shadowRoot"); 469 "shadowRoot");
482 let shadowRoot = Function.prototype.call.bind(desc.get); 470 let shadowRoot = Function.prototype.call.bind(desc.get);
483 471
484 Object.defineProperty(Element.prototype, "shadowRoot", { 472 Object.defineProperty(Element.prototype, "shadowRoot", {
485 configurable: true, enumerable: true, get() 473 configurable: true, enumerable: true, get()
486 { 474 {
487 let thisShadow = shadowRoot(this); 475 let thisShadow = shadowRoot(this);
488 return thisShadow == ourShadowRoot ? null : shadow; 476 return thisShadow == ourShadowRoot ? null : thisShadow;
489 } 477 }
490 }); 478 });
491 }, null); 479 }, null);
492 } 480 }
493 481
494 return shadow; 482 return shadow;
495 }, 483 },
496 484
497 addSelectors(selectors) 485 addSelectors(selectors, filters)
498 { 486 {
499 if (selectors.length == 0) 487 if (selectors.length == 0)
500 return; 488 return;
501 489
502 if (!this.style) 490 if (!this.style)
503 { 491 {
504 // Create <style> element lazily, only if we add styles. Add it to 492 // Create <style> element lazily, only if we add styles. Add it to
505 // the shadow DOM if possible. Otherwise fallback to the <head> or 493 // the shadow DOM if possible. Otherwise fallback to the <head> or
506 // <html> element. If we have injected a style element before that 494 // <html> element. If we have injected a style element before that
507 // has been removed (the sheet property is null), create a new one. 495 // has been removed (the sheet property is null), create a new one.
508 this.style = document.createElement("style"); 496 this.style = document.createElement("style");
509 (this.shadow || document.head || 497 (this.shadow || document.head ||
510 document.documentElement).appendChild(this.style); 498 document.documentElement).appendChild(this.style);
Sebastian Noack 2017/02/09 01:04:50 Is the fallback to document.documentElement even s
kzar 2017/02/20 10:27:31 I have no idea.
511 499
512 // It can happen that the frame already navigated to a different 500 // It can happen that the frame already navigated to a different
513 // document while we were waiting for the background page to respond. 501 // document while we were waiting for the background page to respond.
514 // In that case the sheet property will stay null, after addind the 502 // In that case the sheet property will stay null, after addind the
515 // <style> element to the shadow DOM. 503 // <style> element to the shadow DOM.
516 if (!this.style.sheet) 504 if (!this.style.sheet)
517 return; 505 return;
518 } 506 }
519 507
520 // If using shadow DOM, we have to add the ::content pseudo-element 508 // If using shadow DOM, we have to add the ::content pseudo-element
521 // before each selector, in order to match elements within the 509 // before each selector, in order to match elements within the
522 // insertion point. 510 // insertion point.
511 let preparedSelectors = [];
523 if (this.shadow) 512 if (this.shadow)
524 { 513 {
525 let preparedSelectors = [];
526 for (let selector of selectors) 514 for (let selector of selectors)
527 { 515 {
528 let subSelectors = splitSelector(selector); 516 let subSelectors = splitSelector(selector);
529 for (let subSelector of subSelectors) 517 for (let subSelector of subSelectors)
530 preparedSelectors.push("::content " + subSelector); 518 preparedSelectors.push("::content " + subSelector);
531 } 519 }
532 selectors = preparedSelectors; 520 }
521 else
522 {
523 preparedSelectors = selectors;
533 } 524 }
534 525
535 // Safari only allows 8192 primitive selectors to be injected at once[1], we 526 // Safari only allows 8192 primitive selectors to be injected at once[1], we
536 // therefore chunk the inserted selectors into groups of 200 to be safe. 527 // therefore chunk the inserted selectors into groups of 200 to be safe.
537 // (Chrome also has a limit, larger... but we're not certain exactly what it 528 // (Chrome also has a limit, larger... but we're not certain exactly what it
538 // is! Edge apparently has no such limit.) 529 // is! Edge apparently has no such limit.)
539 // [1] - https://github.com/WebKit/webkit/blob/1cb2227f6b2a1035f7bdc46e5ab69 debb75fc1de/Source/WebCore/css/RuleSet.h#L68 530 // [1] - https://github.com/WebKit/webkit/blob/1cb2227f6b2a1035f7bdc46e5ab69 debb75fc1de/Source/WebCore/css/RuleSet.h#L68
540 for (let i = 0; i < selectors.length; i += this.selectorGroupSize) 531 for (let i = 0; i < preparedSelectors.length; i += this.selectorGroupSize)
541 { 532 {
542 let selector = selectors.slice(i, i + this.selectorGroupSize).join(", "); 533 let selector = preparedSelectors.slice(
534 i, i + this.selectorGroupSize
535 ).join(", ");
543 this.style.sheet.insertRule(selector + "{display: none !important;}", 536 this.style.sheet.insertRule(selector + "{display: none !important;}",
544 this.style.sheet.cssRules.length); 537 this.style.sheet.cssRules.length);
545 } 538 }
539
540 if (this.tracer)
541 this.tracer.addSelectors(selectors, filters || selectors);
546 }, 542 },
547 543
548 apply() 544 apply()
549 { 545 {
550 let selectors = null; 546 ext.backgroundPage.sendMessage({type: "get-selectors"}, response =>
551 let elemHideEmulationLoaded = false; 547 {
552
553 let checkLoaded = function()
554 {
555 if (!selectors || !elemHideEmulationLoaded)
556 return;
557
558 if (this.tracer) 548 if (this.tracer)
559 this.tracer.disconnect(); 549 this.tracer.disconnect();
560 this.tracer = null; 550 this.tracer = null;
561 551
562 if (this.style && this.style.parentElement) 552 if (this.style && this.style.parentElement)
563 this.style.parentElement.removeChild(this.style); 553 this.style.parentElement.removeChild(this.style);
564 this.style = null; 554 this.style = null;
565 555
566 this.addSelectors(selectors.selectors); 556 if (response.trace)
557 this.tracer = new ElementHidingTracer();
558
559 this.addSelectors(response.selectors);
567 this.elemHideEmulation.apply(); 560 this.elemHideEmulation.apply();
568
569 if (selectors.trace)
570 this.tracer = new ElementHidingTracer(selectors.selectors);
571 }.bind(this);
572
573 ext.backgroundPage.sendMessage({type: "get-selectors"}, response =>
574 {
575 selectors = response;
576 checkLoaded();
577 });
578
579 this.elemHideEmulation.load(() =>
580 {
581 elemHideEmulationLoaded = true;
582 checkLoaded();
583 }); 561 });
584 } 562 }
585 }; 563 };
586 564
587 if (document instanceof HTMLDocument) 565 if (document instanceof HTMLDocument)
588 { 566 {
589 checkSitekey(); 567 checkSitekey();
590 wrapWebSocket(); 568 wrapWebSocket();
591 569
592 elemhide = new ElemHide(); 570 elemhide = new ElemHide();
593 elemhide.apply(); 571 elemhide.apply();
594 572
595 document.addEventListener("error", event => 573 document.addEventListener("error", event =>
596 { 574 {
597 checkCollapse(event.target); 575 checkCollapse(event.target);
598 }, true); 576 }, true);
599 577
600 document.addEventListener("load", event => 578 document.addEventListener("load", event =>
601 { 579 {
602 let element = event.target; 580 let element = event.target;
603 if (/^i?frame$/.test(element.localName)) 581 if (/^i?frame$/.test(element.localName))
604 checkCollapse(element); 582 checkCollapse(element);
605 }, true); 583 }, true);
606 } 584 }
LEFTRIGHT

Powered by Google App Engine
This is Rietveld