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: Created April 3, 2017, 2:10 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 109 matching lines...) Expand 10 before | Expand all | Expand 10 after
120 120
121 for (let i = 0; i < urls.length; i++) 121 for (let i = 0; i < urls.length; i++)
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)
131 {
132 function doHide(el)
133 {
134 let propertyName = "display";
135 let propertyValue = "none";
136 if (el.localName == "frame")
137 {
138 propertyName = "visibility";
139 propertyValue = "hidden";
140 }
141
142 if (el.style.getPropertyValue(propertyName) != propertyValue ||
143 el.style.getPropertyPriority(propertyName) != "important")
144 el.style.setProperty(propertyName, propertyValue, "important");
145 }
146
147 doHide(element);
148
149 new MutationObserver(doHide).observe(
150 element, {
151 attributes: true,
152 attributeFilter: ["style"]
153 }
154 );
155 }
156
130 function checkCollapse(element) 157 function checkCollapse(element)
131 { 158 {
132 let mediatype = typeMap.get(element.localName); 159 let mediatype = typeMap.get(element.localName);
133 if (!mediatype) 160 if (!mediatype)
134 return; 161 return;
135 162
136 let urls = getURLsFromElement(element); 163 let urls = getURLsFromElement(element);
137 if (urls.length == 0) 164 if (urls.length == 0)
138 return; 165 return;
139 166
140 ext.backgroundPage.sendMessage( 167 ext.backgroundPage.sendMessage(
141 { 168 {
142 type: "filters.collapse", 169 type: "filters.collapse",
143 urls, 170 urls,
144 mediatype, 171 mediatype,
145 baseURL: document.location.href 172 baseURL: document.location.href
146 }, 173 },
147 174
148 collapse => 175 collapse =>
149 { 176 {
150 function collapseElement()
151 {
152 let propertyName = "display";
153 let propertyValue = "none";
154 if (element.localName == "frame")
155 {
156 propertyName = "visibility";
157 propertyValue = "hidden";
158 }
159
160 if (element.style.getPropertyValue(propertyName) != propertyValue ||
161 element.style.getPropertyPriority(propertyName) != "important")
162 element.style.setProperty(propertyName, propertyValue, "important");
163 }
164
165 if (collapse) 177 if (collapse)
166 { 178 {
167 collapseElement(); 179 hideElement(element);
168
169 new MutationObserver(collapseElement).observe(
170 element, {
171 attributes: true,
172 attributeFilter: ["style"]
173 }
174 );
175 } 180 }
176 } 181 }
177 ); 182 );
178 } 183 }
179 184
180 function checkSitekey() 185 function checkSitekey()
181 { 186 {
182 let attr = document.documentElement.getAttribute("data-adblockkey"); 187 let attr = document.documentElement.getAttribute("data-adblockkey");
183 if (attr) 188 if (attr)
184 ext.backgroundPage.sendMessage({type: "filters.addKey", token: attr}); 189 ext.backgroundPage.sendMessage({type: "filters.addKey", token: attr});
185 } 190 }
186 191
187 function ElementHidingTracer() 192 function ElementHidingTracer()
188 { 193 {
189 this.selectors = []; 194 this.selectors = [];
190 this.filters = [];
191
192 this.changedNodes = []; 195 this.changedNodes = [];
193 this.timeout = null; 196 this.timeout = null;
194
195 this.observer = new MutationObserver(this.observe.bind(this)); 197 this.observer = new MutationObserver(this.observe.bind(this));
196 this.trace = this.trace.bind(this); 198 this.trace = this.trace.bind(this);
197 199
198 if (document.readyState == "loading") 200 if (document.readyState == "loading")
199 document.addEventListener("DOMContentLoaded", this.trace); 201 document.addEventListener("DOMContentLoaded", this.trace);
200 else 202 else
201 this.trace(); 203 this.trace();
202 } 204 }
203 ElementHidingTracer.prototype = { 205 ElementHidingTracer.prototype = {
204 addSelectors(selectors, filters) 206 addSelectors(selectors, filters)
205 { 207 {
208 let pairs = selectors.map((sel, i) => [sel, filters && filters[i]]);
209
206 if (document.readyState != "loading") 210 if (document.readyState != "loading")
207 this.checkNodes([document], selectors, filters); 211 this.checkNodes([document], pairs);
208 212
209 this.selectors.push(...selectors); 213 this.selectors.push(...pairs);
210 this.filters.push(...filters); 214 },
211 }, 215
212 216 checkNodes(nodes, pairs)
213 checkNodes(nodes, selectors, filters) 217 {
214 { 218 let selectors = [];
215 let matchedSelectors = []; 219 let filters = [];
216 220
217 for (let i = 0; i < selectors.length; i++) 221 for (let [selector, filter] of pairs)
218 { 222 {
219 nodes: for (let node of nodes) 223 nodes: for (let node of nodes)
220 { 224 {
221 let elements = node.querySelectorAll(selectors[i]); 225 for (let element of node.querySelectorAll(selector))
222
223 for (let element of elements)
224 { 226 {
225 // Only consider selectors that actually have an effect on the 227 // Only consider selectors that actually have an effect on the
226 // computed styles, and aren't overridden by rules with higher 228 // computed styles, and aren't overridden by rules with higher
227 // priority, or haven't been circumvented in a different way. 229 // priority, or haven't been circumvented in a different way.
228 if (getComputedStyle(element).display == "none") 230 if (getComputedStyle(element).display == "none")
229 { 231 {
230 matchedSelectors.push(filters[i].replace(/^.*?##/, "")); 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
231 break nodes; 242 break nodes;
232 } 243 }
233 } 244 }
234 } 245 }
235 } 246 }
236 247
237 if (matchedSelectors.length > 0) 248 if (selectors.length > 0 || filters.length > 0)
238 { 249 {
239 ext.backgroundPage.sendMessage({ 250 ext.backgroundPage.sendMessage({
240 type: "devtools.traceElemHide", 251 type: "devtools.traceElemHide",
241 selectors: matchedSelectors 252 selectors, filters
242 }); 253 });
243 } 254 }
244 }, 255 },
245 256
246 onTimeout() 257 onTimeout()
247 { 258 {
248 this.checkNodes(this.changedNodes, this.selectors, this.filters); 259 this.checkNodes(this.changedNodes, this.selectors);
249 this.changedNodes = []; 260 this.changedNodes = [];
250 this.timeout = null; 261 this.timeout = null;
251 }, 262 },
252 263
253 observe(mutations) 264 observe(mutations)
254 { 265 {
255 // Forget previously changed nodes that are no longer in the DOM. 266 // Forget previously changed nodes that are no longer in the DOM.
256 for (let i = 0; i < this.changedNodes.length; i++) 267 for (let i = 0; i < this.changedNodes.length; i++)
257 { 268 {
258 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
300 311
301 // Check only nodes whose descendants have changed, and not more often 312 // Check only nodes whose descendants have changed, and not more often
302 // 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
303 // (like YouTube) freeze when the devtools panel is active. 314 // (like YouTube) freeze when the devtools panel is active.
304 if (this.timeout == null) 315 if (this.timeout == null)
305 this.timeout = setTimeout(this.onTimeout.bind(this), 1000); 316 this.timeout = setTimeout(this.onTimeout.bind(this), 1000);
306 }, 317 },
307 318
308 trace() 319 trace()
309 { 320 {
310 this.checkNodes([document], this.selectors, this.filters); 321 this.checkNodes([document], this.selectors);
311 322
312 this.observer.observe( 323 this.observer.observe(
313 document, 324 document,
314 { 325 {
315 childList: true, 326 childList: true,
316 attributes: true, 327 attributes: true,
317 subtree: true 328 subtree: true
318 } 329 }
319 ); 330 );
320 }, 331 },
321 332
322 disconnect() 333 disconnect()
323 { 334 {
324 document.removeEventListener("DOMContentLoaded", this.trace); 335 document.removeEventListener("DOMContentLoaded", this.trace);
325 this.observer.disconnect(); 336 this.observer.disconnect();
326 clearTimeout(this.timeout); 337 clearTimeout(this.timeout);
327 } 338 }
328 }; 339 };
329 340
330 function runInPageContext(fn, arg)
331 {
332 let script = document.createElement("script");
333 script.type = "application/javascript";
334 script.async = false;
335 script.textContent = "(" + fn + ")(" + JSON.stringify(arg) + ");";
336 document.documentElement.appendChild(script);
337 document.documentElement.removeChild(script);
338 }
339
340 // Before Chrome 58 the webRequest API didn't allow us to intercept
341 // WebSockets[1], and therefore some ad networks are misusing them as a way to
342 // serve adverts and circumvent us. As a workaround we wrap WebSocket,
343 // preventing blocked WebSocket connections from being opened.
344 // [1] - https://bugs.chromium.org/p/chromium/issues/detail?id=129353
345 function wrapWebSocket()
346 {
347 let randomEventName = "abpws-" + Math.random().toString(36).substr(2);
348
349 document.addEventListener(randomEventName, event =>
350 {
351 ext.backgroundPage.sendMessage({
352 type: "request.websocket",
353 url: event.detail.url
354 }, block =>
355 {
356 document.dispatchEvent(new CustomEvent(
357 randomEventName + "-" + event.detail.url, {detail: block}
358 ));
359 });
360 });
361
362 runInPageContext(eventName =>
363 {
364 // As far as possible we must track everything we use that could be
365 // sabotaged by the website later in order to circumvent us.
366 let RealWebSocket = WebSocket;
367 let RealCustomEvent = window.CustomEvent;
368 let closeWebSocket = Function.prototype.call.bind(
369 RealWebSocket.prototype.close
370 );
371 let addEventListener = document.addEventListener.bind(document);
372 let removeEventListener = document.removeEventListener.bind(document);
373 let dispatchEvent = document.dispatchEvent.bind(document);
374
375 function checkRequest(url, callback)
376 {
377 let incomingEventName = eventName + "-" + url;
378 function listener(event)
379 {
380 callback(event.detail);
381 removeEventListener(incomingEventName, listener);
382 }
383 addEventListener(incomingEventName, listener);
384
385 dispatchEvent(new RealCustomEvent(eventName, {detail: {url}}));
386 }
387
388 function WrappedWebSocket(url, ...args)
389 {
390 // Throw correct exceptions if the constructor is used improperly.
391 if (!(this instanceof WrappedWebSocket)) return RealWebSocket();
392 if (arguments.length < 1) return new RealWebSocket();
393
394 let websocket = new RealWebSocket(url, ...args);
395
396 checkRequest(websocket.url, blocked =>
397 {
398 if (blocked)
399 closeWebSocket(websocket);
400 });
401
402 return websocket;
403 }
404 WrappedWebSocket.prototype = RealWebSocket.prototype;
405 window.WebSocket = WrappedWebSocket.bind();
406 Object.defineProperties(WebSocket, {
407 CONNECTING: {value: RealWebSocket.CONNECTING, enumerable: true},
408 OPEN: {value: RealWebSocket.OPEN, enumerable: true},
409 CLOSING: {value: RealWebSocket.CLOSING, enumerable: true},
410 CLOSED: {value: RealWebSocket.CLOSED, enumerable: true},
411 prototype: {value: RealWebSocket.prototype}
412 });
413
414 RealWebSocket.prototype.constructor = WebSocket;
415 }, randomEventName);
416 }
417
418 function ElemHide() 341 function ElemHide()
419 { 342 {
420 this.shadow = this.createShadowTree(); 343 this.shadow = this.createShadowTree();
421 this.style = null; 344 this.style = null;
422 this.tracer = null; 345 this.tracer = null;
423 346
424 this.elemHideEmulation = new ElemHideEmulation( 347 this.elemHideEmulation = new ElemHideEmulation(
425 window, 348 window,
426 callback => 349 callback =>
427 { 350 {
(...skipping 23 matching lines...) Expand all
451 if (/\.(?:google|blogger)\.com$/.test(document.domain)) 374 if (/\.(?:google|blogger)\.com$/.test(document.domain))
452 return null; 375 return null;
453 376
454 // 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
455 // 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
456 // avoid creating the shadowRoot twice. 379 // avoid creating the shadowRoot twice.
457 let shadow = document.documentElement.shadowRoot || 380 let shadow = document.documentElement.shadowRoot ||
458 document.documentElement.createShadowRoot(); 381 document.documentElement.createShadowRoot();
459 shadow.appendChild(document.createElement("shadow")); 382 shadow.appendChild(document.createElement("shadow"));
460 383
461 // Stop the website from messing with our shadow root (#4191, #4298).
462 if ("shadowRoot" in Element.prototype)
463 {
464 runInPageContext(() =>
465 {
466 let ourShadowRoot = document.documentElement.shadowRoot;
467 if (!ourShadowRoot)
468 return;
469 let desc = Object.getOwnPropertyDescriptor(Element.prototype,
470 "shadowRoot");
471 let shadowRoot = Function.prototype.call.bind(desc.get);
472
473 Object.defineProperty(Element.prototype, "shadowRoot", {
474 configurable: true, enumerable: true, get()
475 {
476 let thisShadow = shadowRoot(this);
477 return thisShadow == ourShadowRoot ? null : thisShadow;
478 }
479 });
480 }, null);
481 }
482
483 return shadow; 384 return shadow;
484 }, 385 },
485 386
486 addSelectors(selectors, filters) 387 addSelectors(selectors, filters)
487 { 388 {
488 if (selectors.length == 0) 389 if (selectors.length == 0)
489 return; 390 return;
490 391
491 if (!this.style) 392 if (!this.style)
492 { 393 {
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
532 for (let i = 0; i < preparedSelectors.length; i += this.selectorGroupSize) 433 for (let i = 0; i < preparedSelectors.length; i += this.selectorGroupSize)
533 { 434 {
534 let selector = preparedSelectors.slice( 435 let selector = preparedSelectors.slice(
535 i, i + this.selectorGroupSize 436 i, i + this.selectorGroupSize
536 ).join(", "); 437 ).join(", ");
537 this.style.sheet.insertRule(selector + "{display: none !important;}", 438 this.style.sheet.insertRule(selector + "{display: none !important;}",
538 this.style.sheet.cssRules.length); 439 this.style.sheet.cssRules.length);
539 } 440 }
540 441
541 if (this.tracer) 442 if (this.tracer)
542 this.tracer.addSelectors(selectors, filters || selectors); 443 this.tracer.addSelectors(selectors, filters);
543 }, 444 },
544 445
545 hideElements(elements, filters) 446 hideElements(elements, filters)
546 { 447 {
547 for (let element of elements) 448 for (let element of elements)
548 { 449 hideElement(element);
549 element.style.display = "none"; 450
Sebastian Noack 2017/04/04 10:14:45 We should use the same logic as we have in checkCo
hub 2017/04/05 09:05:53 Done.
550 }
551 // XXX first arg is supposed to be selectors
552 if (this.tracer) 451 if (this.tracer)
553 this.tracer.addSelectors(filters, filters); 452 {
Sebastian Noack 2017/04/04 10:14:45 ElementHidingTracer.addSelectors() will search for
hub 2017/04/05 09:05:53 Done.
453 ext.backgroundPage.sendMessage({
454 type: "devtools.traceElemHide",
455 selectors: [],
456 filters
457 });
458 }
554 }, 459 },
555 460
556 apply() 461 apply()
557 { 462 {
558 ext.backgroundPage.sendMessage({type: "get-selectors"}, response => 463 ext.backgroundPage.sendMessage({type: "get-selectors"}, response =>
559 { 464 {
560 if (this.tracer) 465 if (this.tracer)
561 this.tracer.disconnect(); 466 this.tracer.disconnect();
562 this.tracer = null; 467 this.tracer = null;
563 468
564 if (this.style && this.style.parentElement) 469 if (this.style && this.style.parentElement)
565 this.style.parentElement.removeChild(this.style); 470 this.style.parentElement.removeChild(this.style);
566 this.style = null; 471 this.style = null;
567 472
568 if (response.trace) 473 if (response.trace)
569 this.tracer = new ElementHidingTracer(); 474 this.tracer = new ElementHidingTracer();
570 475
571 this.addSelectors(response.selectors); 476 this.addSelectors(response.selectors);
572 this.elemHideEmulation.apply(); 477 this.elemHideEmulation.apply();
573 }); 478 });
574 } 479 }
575 }; 480 };
576 481
577 if (document instanceof HTMLDocument) 482 if (document instanceof HTMLDocument)
578 { 483 {
579 checkSitekey(); 484 checkSitekey();
580 wrapWebSocket();
581 485
582 elemhide = new ElemHide(); 486 elemhide = new ElemHide();
583 elemhide.apply(); 487 elemhide.apply();
584 488
585 document.addEventListener("error", event => 489 document.addEventListener("error", event =>
586 { 490 {
587 checkCollapse(event.target); 491 checkCollapse(event.target);
588 }, true); 492 }, true);
589 493
590 document.addEventListener("load", event => 494 document.addEventListener("load", event =>
591 { 495 {
592 let element = event.target; 496 let element = event.target;
593 if (/^i?frame$/.test(element.localName)) 497 if (/^i?frame$/.test(element.localName))
594 checkCollapse(element); 498 checkCollapse(element);
595 }, true); 499 }, true);
596 } 500 }
LEFTRIGHT

Powered by Google App Engine
This is Rietveld