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: Created Feb. 7, 2017, 4:49 p.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.
23 let elemhide;
24
22 const typeMap = new Map([ 25 const typeMap = new Map([
23 ["img", "IMAGE"], 26 ["img", "IMAGE"],
24 ["input", "IMAGE"], 27 ["input", "IMAGE"],
25 ["picture", "IMAGE"], 28 ["picture", "IMAGE"],
26 ["audio", "MEDIA"], 29 ["audio", "MEDIA"],
27 ["video", "MEDIA"], 30 ["video", "MEDIA"],
28 ["frame", "SUBDOCUMENT"], 31 ["frame", "SUBDOCUMENT"],
29 ["iframe", "SUBDOCUMENT"], 32 ["iframe", "SUBDOCUMENT"],
30 ["object", "OBJECT"], 33 ["object", "OBJECT"],
31 ["embed", "OBJECT"] 34 ["embed", "OBJECT"]
(...skipping 142 matching lines...) Expand 10 before | Expand all | Expand 10 after
174 ); 177 );
175 } 178 }
176 179
177 function checkSitekey() 180 function checkSitekey()
178 { 181 {
179 let attr = document.documentElement.getAttribute("data-adblockkey"); 182 let attr = document.documentElement.getAttribute("data-adblockkey");
180 if (attr) 183 if (attr)
181 ext.backgroundPage.sendMessage({type: "filters.addKey", token: attr}); 184 ext.backgroundPage.sendMessage({type: "filters.addKey", token: attr});
182 } 185 }
183 186
184 function getContentDocument(element) 187 function ElementHidingTracer()
185 { 188 {
186 try 189 this.selectors = [];
187 { 190 this.filters = [];
188 return element.contentDocument;
189 }
190 catch (e)
191 {
192 return null;
193 }
194 }
195
196 function ElementHidingTracer(selectors)
197 {
198 this.selectors = selectors;
199 191
200 this.changedNodes = []; 192 this.changedNodes = [];
201 this.timeout = null; 193 this.timeout = null;
202 194
203 this.observer = new MutationObserver(this.observe.bind(this)); 195 this.observer = new MutationObserver(this.observe.bind(this));
204 this.trace = this.trace.bind(this); 196 this.trace = this.trace.bind(this);
205 197
206 if (document.readyState == "loading") 198 if (document.readyState == "loading")
207 document.addEventListener("DOMContentLoaded", this.trace); 199 document.addEventListener("DOMContentLoaded", this.trace);
208 else 200 else
209 this.trace(); 201 this.trace();
210 } 202 }
211 ElementHidingTracer.prototype = { 203 ElementHidingTracer.prototype = {
212 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)
213 { 214 {
214 let matchedSelectors = []; 215 let matchedSelectors = [];
215 216
216 // Find all selectors that match any hidden element inside the given nodes. 217 for (let i = 0; i < selectors.length; i++)
217 for (let selector of this.selectors) 218 {
218 { 219 nodes: for (let node of nodes)
219 for (let node of nodes) 220 {
220 { 221 let elements = node.querySelectorAll(selectors[i]);
221 let elements = node.querySelectorAll(selector);
222 let matched = false;
223 222
224 for (let element of elements) 223 for (let element of elements)
225 { 224 {
226 // Only consider selectors that actually have an effect on the 225 // Only consider selectors that actually have an effect on the
227 // computed styles, and aren't overridden by rules with higher 226 // computed styles, and aren't overridden by rules with higher
228 // priority, or haven't been circumvented in a different way. 227 // priority, or haven't been circumvented in a different way.
229 if (getComputedStyle(element).display == "none") 228 if (getComputedStyle(element).display == "none")
230 { 229 {
231 matchedSelectors.push(selector); 230 matchedSelectors.push(filters[i].replace(/^.*?##/, ""));
232 matched = true; 231 break nodes;
233 break;
234 } 232 }
235 } 233 }
236
237 if (matched)
238 break;
239 } 234 }
240 } 235 }
241 236
242 if (matchedSelectors.length > 0) 237 if (matchedSelectors.length > 0)
243 { 238 {
244 ext.backgroundPage.sendMessage({ 239 ext.backgroundPage.sendMessage({
245 type: "devtools.traceElemHide", 240 type: "devtools.traceElemHide",
246 selectors: matchedSelectors 241 selectors: matchedSelectors
247 }); 242 });
248 } 243 }
249 }, 244 },
250 245
251 onTimeout() 246 onTimeout()
252 { 247 {
253 this.checkNodes(this.changedNodes); 248 this.checkNodes(this.changedNodes, this.selectors, this.filters);
254 this.changedNodes = []; 249 this.changedNodes = [];
255 this.timeout = null; 250 this.timeout = null;
256 }, 251 },
257 252
258 observe(mutations) 253 observe(mutations)
259 { 254 {
260 // Forget previously changed nodes that are no longer in the DOM. 255 // Forget previously changed nodes that are no longer in the DOM.
261 for (let i = 0; i < this.changedNodes.length; i++) 256 for (let i = 0; i < this.changedNodes.length; i++)
262 { 257 {
263 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
305 300
306 // Check only nodes whose descendants have changed, and not more often 301 // Check only nodes whose descendants have changed, and not more often
307 // 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
308 // (like YouTube) freeze when the devtools panel is active. 303 // (like YouTube) freeze when the devtools panel is active.
309 if (this.timeout == null) 304 if (this.timeout == null)
310 this.timeout = setTimeout(this.onTimeout.bind(this), 1000); 305 this.timeout = setTimeout(this.onTimeout.bind(this), 1000);
311 }, 306 },
312 307
313 trace() 308 trace()
314 { 309 {
315 this.checkNodes([document]); 310 this.checkNodes([document], this.selectors, this.filters);
316 311
317 this.observer.observe( 312 this.observer.observe(
318 document, 313 document,
319 { 314 {
320 childList: true, 315 childList: true,
321 attributes: true, 316 attributes: true,
322 subtree: true 317 subtree: true
323 } 318 }
324 ); 319 );
325 }, 320 },
326 321
327 disconnect() 322 disconnect()
328 { 323 {
329 document.removeEventListener("DOMContentLoaded", this.trace); 324 document.removeEventListener("DOMContentLoaded", this.trace);
330 this.observer.disconnect(); 325 this.observer.disconnect();
331 clearTimeout(this.timeout); 326 clearTimeout(this.timeout);
332 } 327 }
333 }; 328 };
334 329
335 function runInPageContext(fn, arg) 330 function runInPageContext(fn, arg)
336 { 331 {
337 let script = document.createElement("script"); 332 let script = document.createElement("script");
338 script.type = "application/javascript"; 333 script.type = "application/javascript";
339 script.async = false; 334 script.async = false;
340 script.textContent = "(" + fn + ")(" + JSON.stringify(arg) + ");"; 335 script.textContent = "(" + fn + ")(" + JSON.stringify(arg) + ");";
341 document.documentElement.appendChild(script); 336 document.documentElement.appendChild(script);
342 document.documentElement.removeChild(script); 337 document.documentElement.removeChild(script);
343 } 338 }
344 339
345 // Chrome doesn't allow us to intercept WebSockets[1], and therefore 340 // Before Chrome 58 the webRequest API didn't allow us to intercept
346 // 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
347 // us. As a workaround we wrap WebSocket, preventing blocked WebSocket 342 // serve adverts and circumvent us. As a workaround we wrap WebSocket,
348 // connections from being opened. 343 // preventing blocked WebSocket connections from being opened.
349 // [1] - https://bugs.chromium.org/p/chromium/issues/detail?id=129353 344 // [1] - https://bugs.chromium.org/p/chromium/issues/detail?id=129353
350 function wrapWebSocket() 345 function wrapWebSocket()
351 { 346 {
352 let randomEventName = "abpws-" + Math.random().toString(36).substr(2); 347 let randomEventName = "abpws-" + Math.random().toString(36).substr(2);
353 348
354 document.addEventListener(randomEventName, event => 349 document.addEventListener(randomEventName, event =>
355 { 350 {
356 ext.backgroundPage.sendMessage({ 351 ext.backgroundPage.sendMessage({
357 type: "request.websocket", 352 type: "request.websocket",
358 url: event.detail.url 353 url: event.detail.url
(...skipping 30 matching lines...) Expand all
389 384
390 dispatchEvent(new RealCustomEvent(eventName, {detail: {url}})); 385 dispatchEvent(new RealCustomEvent(eventName, {detail: {url}}));
391 } 386 }
392 387
393 function WrappedWebSocket(url, ...args) 388 function WrappedWebSocket(url, ...args)
394 { 389 {
395 // Throw correct exceptions if the constructor is used improperly. 390 // Throw correct exceptions if the constructor is used improperly.
396 if (!(this instanceof WrappedWebSocket)) return RealWebSocket(); 391 if (!(this instanceof WrappedWebSocket)) return RealWebSocket();
397 if (arguments.length < 1) return new RealWebSocket(); 392 if (arguments.length < 1) return new RealWebSocket();
398 393
399 let websocket; 394 let websocket = new RealWebSocket(url, ...args);
400 if (arguments.length == 1)
401 websocket = new RealWebSocket(url);
402 else
403 websocket = new RealWebSocket(url, args[0]);
404 395
405 checkRequest(websocket.url, blocked => 396 checkRequest(websocket.url, blocked =>
406 { 397 {
407 if (blocked) 398 if (blocked)
408 closeWebSocket(websocket); 399 closeWebSocket(websocket);
409 }); 400 });
410 401
411 return websocket; 402 return websocket;
412 } 403 }
413 WrappedWebSocket.prototype = RealWebSocket.prototype; 404 WrappedWebSocket.prototype = RealWebSocket.prototype;
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after
475 if (!ourShadowRoot) 466 if (!ourShadowRoot)
476 return; 467 return;
477 let desc = Object.getOwnPropertyDescriptor(Element.prototype, 468 let desc = Object.getOwnPropertyDescriptor(Element.prototype,
478 "shadowRoot"); 469 "shadowRoot");
479 let shadowRoot = Function.prototype.call.bind(desc.get); 470 let shadowRoot = Function.prototype.call.bind(desc.get);
480 471
481 Object.defineProperty(Element.prototype, "shadowRoot", { 472 Object.defineProperty(Element.prototype, "shadowRoot", {
482 configurable: true, enumerable: true, get() 473 configurable: true, enumerable: true, get()
483 { 474 {
484 let thisShadow = shadowRoot(this); 475 let thisShadow = shadowRoot(this);
485 return thisShadow == ourShadowRoot ? null : shadow; 476 return thisShadow == ourShadowRoot ? null : thisShadow;
486 } 477 }
487 }); 478 });
488 }, null); 479 }, null);
489 } 480 }
490 481
491 return shadow; 482 return shadow;
492 }, 483 },
493 484
494 addSelectors(selectors) 485 addSelectors(selectors, filters)
495 { 486 {
496 if (selectors.length == 0) 487 if (selectors.length == 0)
497 return; 488 return;
498 489
499 if (!this.style) 490 if (!this.style)
500 { 491 {
501 // 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
502 // the shadow DOM if possible. Otherwise fallback to the <head> or 493 // the shadow DOM if possible. Otherwise fallback to the <head> or
503 // <html> element. If we have injected a style element before that 494 // <html> element. If we have injected a style element before that
504 // 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.
505 this.style = document.createElement("style"); 496 this.style = document.createElement("style");
506 (this.shadow || document.head || 497 (this.shadow || document.head ||
507 document.documentElement).appendChild(this.style); 498 document.documentElement).appendChild(this.style);
508 499
509 // It can happen that the frame already navigated to a different 500 // It can happen that the frame already navigated to a different
510 // document while we were waiting for the background page to respond. 501 // document while we were waiting for the background page to respond.
511 // 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
512 // <style> element to the shadow DOM. 503 // <style> element to the shadow DOM.
513 if (!this.style.sheet) 504 if (!this.style.sheet)
514 return; 505 return;
515 } 506 }
516 507
517 // 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
518 // before each selector, in order to match elements within the 509 // before each selector, in order to match elements within the
519 // insertion point. 510 // insertion point.
511 let preparedSelectors = [];
520 if (this.shadow) 512 if (this.shadow)
521 { 513 {
522 let preparedSelectors = [];
523 for (let selector of selectors) 514 for (let selector of selectors)
524 { 515 {
525 let subSelectors = splitSelector(selector); 516 let subSelectors = splitSelector(selector);
526 for (let subSelector of subSelectors) 517 for (let subSelector of subSelectors)
527 preparedSelectors.push("::content " + subSelector); 518 preparedSelectors.push("::content " + subSelector);
528 } 519 }
529 selectors = preparedSelectors; 520 }
521 else
522 {
523 preparedSelectors = selectors;
530 } 524 }
531 525
532 // 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
533 // 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.
534 // (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
535 // is! Edge apparently has no such limit.) 529 // is! Edge apparently has no such limit.)
536 // [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
537 for (let i = 0; i < selectors.length; i += this.selectorGroupSize) 531 for (let i = 0; i < preparedSelectors.length; i += this.selectorGroupSize)
538 { 532 {
539 let selector = selectors.slice(i, i + this.selectorGroupSize).join(", "); 533 let selector = preparedSelectors.slice(
534 i, i + this.selectorGroupSize
535 ).join(", ");
540 this.style.sheet.insertRule(selector + "{display: none !important;}", 536 this.style.sheet.insertRule(selector + "{display: none !important;}",
541 this.style.sheet.cssRules.length); 537 this.style.sheet.cssRules.length);
542 } 538 }
539
540 if (this.tracer)
541 this.tracer.addSelectors(selectors, filters || selectors);
543 }, 542 },
544 543
545 apply() 544 apply()
546 { 545 {
547 let selectors = null; 546 ext.backgroundPage.sendMessage({type: "get-selectors"}, response =>
548 let elemHideEmulationLoaded = false; 547 {
549
550 let checkLoaded = function()
551 {
552 if (!selectors || !elemHideEmulationLoaded)
553 return;
554
555 if (this.tracer) 548 if (this.tracer)
556 this.tracer.disconnect(); 549 this.tracer.disconnect();
557 this.tracer = null; 550 this.tracer = null;
558 551
559 if (this.style && this.style.parentElement) 552 if (this.style && this.style.parentElement)
560 this.style.parentElement.removeChild(this.style); 553 this.style.parentElement.removeChild(this.style);
561 this.style = null; 554 this.style = null;
562 555
563 this.addSelectors(selectors.selectors); 556 if (response.trace)
557 this.tracer = new ElementHidingTracer();
558
559 this.addSelectors(response.selectors);
564 this.elemHideEmulation.apply(); 560 this.elemHideEmulation.apply();
565
566 if (selectors.trace)
567 this.tracer = new ElementHidingTracer(selectors.selectors);
568 }.bind(this);
569
570 ext.backgroundPage.sendMessage({type: "get-selectors"}, response =>
571 {
572 selectors = response;
573 checkLoaded();
574 });
575
576 this.elemHideEmulation.load(() =>
577 {
578 elemHideEmulationLoaded = true;
579 checkLoaded();
580 }); 561 });
581 } 562 }
582 }; 563 };
583 564
584 if (document instanceof HTMLDocument) 565 if (document instanceof HTMLDocument)
585 { 566 {
586 checkSitekey(); 567 checkSitekey();
587 wrapWebSocket(); 568 wrapWebSocket();
588 569
589 // This variable is also used by our other content scripts, outside of the 570 elemhide = new ElemHide();
590 // current scope.
591 /* eslint-disable no-var */
592 var elemhide = new ElemHide();
593 /* eslint-enable no-var */
594 elemhide.apply(); 571 elemhide.apply();
595 572
596 document.addEventListener("error", event => 573 document.addEventListener("error", event =>
597 { 574 {
598 checkCollapse(event.target); 575 checkCollapse(event.target);
599 }, true); 576 }, true);
600 577
601 document.addEventListener("load", event => 578 document.addEventListener("load", event =>
602 { 579 {
603 let element = event.target; 580 let element = event.target;
604 if (/^i?frame$/.test(element.localName)) 581 if (/^i?frame$/.test(element.localName))
605 checkCollapse(element); 582 checkCollapse(element);
606 }, true); 583 }, true);
607 } 584 }
LEFTRIGHT

Powered by Google App Engine
This is Rietveld