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

Delta Between Two Patch Sets: include.preload.js

Issue 29401596: Issue 5094 - Implement support for :has() in chrome extension (Closed) Base URL: https://hg.adblockplus.org/adblockpluschrome/
Left Patch Set: Changes following the feedback. Created April 6, 2017, 3:40 p.m.
Right Patch Set: Update dependencies to the latest. Created June 19, 2017, 2:12 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/filterValidation.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-2017 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
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after
122 { 122 {
123 if (/^(?!https?:)[\w-]+:/i.test(urls[i])) 123 if (/^(?!https?:)[\w-]+:/i.test(urls[i]))
124 urls.splice(i--, 1); 124 urls.splice(i--, 1);
125 } 125 }
126 126
127 return urls; 127 return urls;
128 } 128 }
129 129
130 function hideElement(element) 130 function hideElement(element)
131 { 131 {
132 function doHide(element) 132 function doHide(el)
133 { 133 {
134 let propertyName = "display"; 134 let propertyName = "display";
135 let propertyValue = "none"; 135 let propertyValue = "none";
136 if (element.localName == "frame") 136 if (el.localName == "frame")
137 { 137 {
138 propertyName = "visibility"; 138 propertyName = "visibility";
139 propertyValue = "hidden"; 139 propertyValue = "hidden";
140 } 140 }
141 141
142 if (element.style.getPropertyValue(propertyName) != propertyValue || 142 if (el.style.getPropertyValue(propertyName) != propertyValue ||
143 element.style.getPropertyPriority(propertyName) != "important") 143 el.style.getPropertyPriority(propertyName) != "important")
144 element.style.setProperty(propertyName, propertyValue, "important"); 144 el.style.setProperty(propertyName, propertyValue, "important");
145 } 145 }
146 146
147 doHide(element); 147 doHide(element);
148 148
149 new MutationObserver(doHide).observe( 149 new MutationObserver(doHide).observe(
150 element, { 150 element, {
151 attributes: true, 151 attributes: true,
152 attributeFilter: ["style"] 152 attributeFilter: ["style"]
153 } 153 }
154 ); 154 );
(...skipping 30 matching lines...) Expand all
185 function checkSitekey() 185 function checkSitekey()
186 { 186 {
187 let attr = document.documentElement.getAttribute("data-adblockkey"); 187 let attr = document.documentElement.getAttribute("data-adblockkey");
188 if (attr) 188 if (attr)
189 ext.backgroundPage.sendMessage({type: "filters.addKey", token: attr}); 189 ext.backgroundPage.sendMessage({type: "filters.addKey", token: attr});
190 } 190 }
191 191
192 function ElementHidingTracer() 192 function ElementHidingTracer()
193 { 193 {
194 this.selectors = []; 194 this.selectors = [];
195 this.filters = [];
196
197 this.changedNodes = []; 195 this.changedNodes = [];
198 this.timeout = null; 196 this.timeout = null;
199
200 this.observer = new MutationObserver(this.observe.bind(this)); 197 this.observer = new MutationObserver(this.observe.bind(this));
201 this.trace = this.trace.bind(this); 198 this.trace = this.trace.bind(this);
202 199
203 if (document.readyState == "loading") 200 if (document.readyState == "loading")
204 document.addEventListener("DOMContentLoaded", this.trace); 201 document.addEventListener("DOMContentLoaded", this.trace);
205 else 202 else
206 this.trace(); 203 this.trace();
207 } 204 }
208 ElementHidingTracer.prototype = { 205 ElementHidingTracer.prototype = {
209 addSelectors(selectors, filters) 206 addSelectors(selectors, filters)
210 { 207 {
208 let pairs = selectors.map((sel, i) => [sel, filters && filters[i]]);
209
211 if (document.readyState != "loading") 210 if (document.readyState != "loading")
212 this.checkNodes([document], selectors, filters); 211 this.checkNodes([document], pairs);
213 212
214 this.selectors.push(...selectors); 213 this.selectors.push(...pairs);
215 this.filters.push(...filters); 214 },
216 }, 215
217 216 checkNodes(nodes, pairs)
218 reportFilters(filters) 217 {
219 { 218 let selectors = [];
220 let matchedSelectors = []; 219 let filters = [];
221 for (let filter of filters) 220
222 matchedSelectors.push(filter.replace(/^.*?##/, "")); 221 for (let [selector, filter] of pairs)
223
224 ext.backgroundPage.sendMessage({
225 type: "devtools.traceElemHide",
226 selectors: matchedSelectors
227 });
228 },
229
230 checkNodes(nodes, selectors, filters)
231 {
232 let matchedSelectors = [];
233
234 for (let i = 0; i < selectors.length; i++)
235 { 222 {
236 nodes: for (let node of nodes) 223 nodes: for (let node of nodes)
237 { 224 {
238 let elements = node.querySelectorAll(selectors[i]); 225 for (let element of node.querySelectorAll(selector))
239
240 for (let element of elements)
241 { 226 {
242 // Only consider selectors that actually have an effect on the 227 // Only consider selectors that actually have an effect on the
243 // computed styles, and aren't overridden by rules with higher 228 // computed styles, and aren't overridden by rules with higher
244 // priority, or haven't been circumvented in a different way. 229 // priority, or haven't been circumvented in a different way.
245 if (getComputedStyle(element).display == "none") 230 if (getComputedStyle(element).display == "none")
246 { 231 {
247 matchedSelectors.push(filters[i]); 232 // For regular element hiding, we don't know the exact filter,
233 // but the background page can find it with the given selector.
234 // In case of element hiding emulation, the generated selector
235 // we got here is different from the selector part of the filter,
236 // but in this case we can send the whole filter text instead.
237 if (filter)
238 filters.push(filter);
239 else
240 selectors.push(selector);
241
248 break nodes; 242 break nodes;
249 } 243 }
250 } 244 }
251 } 245 }
252 } 246 }
253 247
254 if (matchedSelectors.length > 0) 248 if (selectors.length > 0 || filters.length > 0)
255 this.reportFilters(matchedSelectors); 249 {
250 ext.backgroundPage.sendMessage({
251 type: "devtools.traceElemHide",
252 selectors, filters
253 });
254 }
256 }, 255 },
257 256
258 onTimeout() 257 onTimeout()
259 { 258 {
260 this.checkNodes(this.changedNodes, this.selectors, this.filters); 259 this.checkNodes(this.changedNodes, this.selectors);
261 this.changedNodes = []; 260 this.changedNodes = [];
262 this.timeout = null; 261 this.timeout = null;
263 }, 262 },
264 263
265 observe(mutations) 264 observe(mutations)
266 { 265 {
267 // Forget previously changed nodes that are no longer in the DOM. 266 // Forget previously changed nodes that are no longer in the DOM.
268 for (let i = 0; i < this.changedNodes.length; i++) 267 for (let i = 0; i < this.changedNodes.length; i++)
269 { 268 {
270 if (!document.contains(this.changedNodes[i])) 269 if (!document.contains(this.changedNodes[i]))
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
312 311
313 // Check only nodes whose descendants have changed, and not more often 312 // Check only nodes whose descendants have changed, and not more often
314 // than once a second. Otherwise large pages with a lot of DOM mutations 313 // than once a second. Otherwise large pages with a lot of DOM mutations
315 // (like YouTube) freeze when the devtools panel is active. 314 // (like YouTube) freeze when the devtools panel is active.
316 if (this.timeout == null) 315 if (this.timeout == null)
317 this.timeout = setTimeout(this.onTimeout.bind(this), 1000); 316 this.timeout = setTimeout(this.onTimeout.bind(this), 1000);
318 }, 317 },
319 318
320 trace() 319 trace()
321 { 320 {
322 this.checkNodes([document], this.selectors, this.filters); 321 this.checkNodes([document], this.selectors);
323 322
324 this.observer.observe( 323 this.observer.observe(
325 document, 324 document,
326 { 325 {
327 childList: true, 326 childList: true,
328 attributes: true, 327 attributes: true,
329 subtree: true 328 subtree: true
330 } 329 }
331 ); 330 );
332 }, 331 },
333 332
334 disconnect() 333 disconnect()
335 { 334 {
336 document.removeEventListener("DOMContentLoaded", this.trace); 335 document.removeEventListener("DOMContentLoaded", this.trace);
337 this.observer.disconnect(); 336 this.observer.disconnect();
338 clearTimeout(this.timeout); 337 clearTimeout(this.timeout);
339 } 338 }
340 }; 339 };
341 340
342 function runInPageContext(fn, arg)
343 {
344 let script = document.createElement("script");
345 script.type = "application/javascript";
346 script.async = false;
347 script.textContent = "(" + fn + ")(" + JSON.stringify(arg) + ");";
348 document.documentElement.appendChild(script);
349 document.documentElement.removeChild(script);
350 }
351
352 // Before Chrome 58 the webRequest API didn't allow us to intercept
353 // WebSockets[1], and therefore some ad networks are misusing them as a way to
354 // serve adverts and circumvent us. As a workaround we wrap WebSocket,
355 // preventing blocked WebSocket connections from being opened.
356 // [1] - https://bugs.chromium.org/p/chromium/issues/detail?id=129353
357 function wrapWebSocket()
358 {
359 let randomEventName = "abpws-" + Math.random().toString(36).substr(2);
360
361 document.addEventListener(randomEventName, event =>
362 {
363 ext.backgroundPage.sendMessage({
364 type: "request.websocket",
365 url: event.detail.url
366 }, block =>
367 {
368 document.dispatchEvent(new CustomEvent(
369 randomEventName + "-" + event.detail.url, {detail: block}
370 ));
371 });
372 });
373
374 runInPageContext(eventName =>
375 {
376 // As far as possible we must track everything we use that could be
377 // sabotaged by the website later in order to circumvent us.
378 let RealWebSocket = WebSocket;
379 let RealCustomEvent = window.CustomEvent;
380 let closeWebSocket = Function.prototype.call.bind(
381 RealWebSocket.prototype.close
382 );
383 let addEventListener = document.addEventListener.bind(document);
384 let removeEventListener = document.removeEventListener.bind(document);
385 let dispatchEvent = document.dispatchEvent.bind(document);
386
387 function checkRequest(url, callback)
388 {
389 let incomingEventName = eventName + "-" + url;
390 function listener(event)
391 {
392 callback(event.detail);
393 removeEventListener(incomingEventName, listener);
394 }
395 addEventListener(incomingEventName, listener);
396
397 dispatchEvent(new RealCustomEvent(eventName, {detail: {url}}));
398 }
399
400 function WrappedWebSocket(url, ...args)
401 {
402 // Throw correct exceptions if the constructor is used improperly.
403 if (!(this instanceof WrappedWebSocket)) return RealWebSocket();
404 if (arguments.length < 1) return new RealWebSocket();
405
406 let websocket = new RealWebSocket(url, ...args);
407
408 checkRequest(websocket.url, blocked =>
409 {
410 if (blocked)
411 closeWebSocket(websocket);
412 });
413
414 return websocket;
415 }
416 WrappedWebSocket.prototype = RealWebSocket.prototype;
417 window.WebSocket = WrappedWebSocket.bind();
418 Object.defineProperties(WebSocket, {
419 CONNECTING: {value: RealWebSocket.CONNECTING, enumerable: true},
420 OPEN: {value: RealWebSocket.OPEN, enumerable: true},
421 CLOSING: {value: RealWebSocket.CLOSING, enumerable: true},
422 CLOSED: {value: RealWebSocket.CLOSED, enumerable: true},
423 prototype: {value: RealWebSocket.prototype}
424 });
425
426 RealWebSocket.prototype.constructor = WebSocket;
427 }, randomEventName);
428 }
429
430 function ElemHide() 341 function ElemHide()
431 { 342 {
432 this.shadow = this.createShadowTree(); 343 this.shadow = this.createShadowTree();
433 this.style = null; 344 this.style = null;
434 this.tracer = null; 345 this.tracer = null;
435 346
436 this.elemHideEmulation = new ElemHideEmulation( 347 this.elemHideEmulation = new ElemHideEmulation(
437 window, 348 window,
438 callback => 349 callback =>
439 { 350 {
(...skipping 23 matching lines...) Expand all
463 if (/\.(?:google|blogger)\.com$/.test(document.domain)) 374 if (/\.(?:google|blogger)\.com$/.test(document.domain))
464 return null; 375 return null;
465 376
466 // Finally since some users have both AdBlock and Adblock Plus installed we 377 // Finally since some users have both AdBlock and Adblock Plus installed we
467 // have to consider how the two extensions interact. For example we want to 378 // have to consider how the two extensions interact. For example we want to
468 // avoid creating the shadowRoot twice. 379 // avoid creating the shadowRoot twice.
469 let shadow = document.documentElement.shadowRoot || 380 let shadow = document.documentElement.shadowRoot ||
470 document.documentElement.createShadowRoot(); 381 document.documentElement.createShadowRoot();
471 shadow.appendChild(document.createElement("shadow")); 382 shadow.appendChild(document.createElement("shadow"));
472 383
473 // Stop the website from messing with our shadow root (#4191, #4298).
474 if ("shadowRoot" in Element.prototype)
475 {
476 runInPageContext(() =>
477 {
478 let ourShadowRoot = document.documentElement.shadowRoot;
479 if (!ourShadowRoot)
480 return;
481 let desc = Object.getOwnPropertyDescriptor(Element.prototype,
482 "shadowRoot");
483 let shadowRoot = Function.prototype.call.bind(desc.get);
484
485 Object.defineProperty(Element.prototype, "shadowRoot", {
486 configurable: true, enumerable: true, get()
487 {
488 let thisShadow = shadowRoot(this);
489 return thisShadow == ourShadowRoot ? null : thisShadow;
490 }
491 });
492 }, null);
493 }
494
495 return shadow; 384 return shadow;
496 }, 385 },
497 386
498 addSelectors(selectors, filters) 387 addSelectors(selectors, filters)
499 { 388 {
500 if (selectors.length == 0) 389 if (selectors.length == 0)
501 return; 390 return;
502 391
503 if (!this.style) 392 if (!this.style)
504 { 393 {
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
544 for (let i = 0; i < preparedSelectors.length; i += this.selectorGroupSize) 433 for (let i = 0; i < preparedSelectors.length; i += this.selectorGroupSize)
545 { 434 {
546 let selector = preparedSelectors.slice( 435 let selector = preparedSelectors.slice(
547 i, i + this.selectorGroupSize 436 i, i + this.selectorGroupSize
548 ).join(", "); 437 ).join(", ");
549 this.style.sheet.insertRule(selector + "{display: none !important;}", 438 this.style.sheet.insertRule(selector + "{display: none !important;}",
550 this.style.sheet.cssRules.length); 439 this.style.sheet.cssRules.length);
551 } 440 }
552 441
553 if (this.tracer) 442 if (this.tracer)
554 this.tracer.addSelectors(selectors, filters || selectors); 443 this.tracer.addSelectors(selectors, filters);
555 }, 444 },
556 445
557 hideElements(elements, filters) 446 hideElements(elements, filters)
558 { 447 {
559 for (let element of elements) 448 for (let element of elements)
560 hideElement(element); 449 hideElement(element);
561 450
562 if (this.tracer) 451 if (this.tracer)
563 this.tracer.reportFilters(filters); 452 {
453 ext.backgroundPage.sendMessage({
454 type: "devtools.traceElemHide",
455 selectors: [],
456 filters
457 });
458 }
564 }, 459 },
565 460
566 apply() 461 apply()
567 { 462 {
568 ext.backgroundPage.sendMessage({type: "get-selectors"}, response => 463 ext.backgroundPage.sendMessage({type: "get-selectors"}, response =>
569 { 464 {
570 if (this.tracer) 465 if (this.tracer)
571 this.tracer.disconnect(); 466 this.tracer.disconnect();
572 this.tracer = null; 467 this.tracer = null;
573 468
574 if (this.style && this.style.parentElement) 469 if (this.style && this.style.parentElement)
575 this.style.parentElement.removeChild(this.style); 470 this.style.parentElement.removeChild(this.style);
576 this.style = null; 471 this.style = null;
577 472
578 if (response.trace) 473 if (response.trace)
579 this.tracer = new ElementHidingTracer(); 474 this.tracer = new ElementHidingTracer();
580 475
581 this.addSelectors(response.selectors); 476 this.addSelectors(response.selectors);
582 this.elemHideEmulation.apply(); 477 this.elemHideEmulation.apply();
583 }); 478 });
584 } 479 }
585 }; 480 };
586 481
587 if (document instanceof HTMLDocument) 482 if (document instanceof HTMLDocument)
588 { 483 {
589 checkSitekey(); 484 checkSitekey();
590 wrapWebSocket();
591 485
592 elemhide = new ElemHide(); 486 elemhide = new ElemHide();
593 elemhide.apply(); 487 elemhide.apply();
594 488
595 document.addEventListener("error", event => 489 document.addEventListener("error", event =>
596 { 490 {
597 checkCollapse(event.target); 491 checkCollapse(event.target);
598 }, true); 492 }, true);
599 493
600 document.addEventListener("load", event => 494 document.addEventListener("load", event =>
601 { 495 {
602 let element = event.target; 496 let element = event.target;
603 if (/^i?frame$/.test(element.localName)) 497 if (/^i?frame$/.test(element.localName))
604 checkCollapse(element); 498 checkCollapse(element);
605 }, true); 499 }, true);
606 } 500 }
LEFTRIGHT

Powered by Google App Engine
This is Rietveld