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

Powered by Google App Engine
This is Rietveld