LEFT | RIGHT |
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-present eyeo GmbH | 3 * Copyright (C) 2006-present 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 159 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
170 } | 170 } |
171 return all ? subtree.querySelectorAll(selector) : | 171 return all ? subtree.querySelectorAll(selector) : |
172 subtree.querySelector(selector); | 172 subtree.querySelector(selector); |
173 } | 173 } |
174 | 174 |
175 function scopedQuerySelectorAll(subtree, selector) | 175 function scopedQuerySelectorAll(subtree, selector) |
176 { | 176 { |
177 return scopedQuerySelector(subtree, selector, true); | 177 return scopedQuerySelector(subtree, selector, true); |
178 } | 178 } |
179 | 179 |
180 const regexpRegexp = /^\/(.*)\/([im]*)$/; | 180 const regexpRegexp = /^\/(.*)\/([imu]*)$/; |
181 | 181 |
182 /** | 182 /** |
183 * Make a regular expression from a text argument. If it can be parsed as a | 183 * Make a regular expression from a text argument. If it can be parsed as a |
184 * regular expression, parse it and the flags. | 184 * regular expression, parse it and the flags. |
185 * @param {string} text the text argument. | 185 * @param {string} text the text argument. |
186 * @return {?RegExp} a RegExp object or null in case of error. | 186 * @return {?RegExp} a RegExp object or null in case of error. |
187 */ | 187 */ |
188 function makeRegExpParameter(text) | 188 function makeRegExpParameter(text) |
189 { | 189 { |
190 let [, pattern, flags] = | 190 let [, pattern, flags] = |
(...skipping 27 matching lines...) Expand all Loading... |
218 // Just in case the getSelectors() generator above had to run some heavy | 218 // Just in case the getSelectors() generator above had to run some heavy |
219 // document.querySelectorAll() call which didn't produce any results, make | 219 // document.querySelectorAll() call which didn't produce any results, make |
220 // sure there is at least one point where execution can pause. | 220 // sure there is at least one point where execution can pause. |
221 yield null; | 221 yield null; |
222 } | 222 } |
223 | 223 |
224 function PlainSelector(selector) | 224 function PlainSelector(selector) |
225 { | 225 { |
226 this._selector = selector; | 226 this._selector = selector; |
227 this.maybeDependsOnAttributes = /[#.]|\[.+\]/.test(selector); | 227 this.maybeDependsOnAttributes = /[#.]|\[.+\]/.test(selector); |
| 228 this.dependsOnDOM = this.maybeDependsOnAttributes; |
228 } | 229 } |
229 | 230 |
230 PlainSelector.prototype = { | 231 PlainSelector.prototype = { |
231 /** | 232 /** |
232 * Generator function returning a pair of selector | 233 * Generator function returning a pair of selector |
233 * string and subtree. | 234 * string and subtree. |
234 * @param {string} prefix the prefix for the selector. | 235 * @param {string} prefix the prefix for the selector. |
235 * @param {Node} subtree the subtree we work on. | 236 * @param {Node} subtree the subtree we work on. |
236 * @param {StringifiedStyle[]} styles the stringified style objects. | 237 * @param {StringifiedStyle[]} styles the stringified style objects. |
237 */ | 238 */ |
238 *getSelectors(prefix, subtree, styles) | 239 *getSelectors(prefix, subtree, styles) |
239 { | 240 { |
240 yield [prefix + this._selector, subtree]; | 241 yield [prefix + this._selector, subtree]; |
241 } | 242 } |
242 }; | 243 }; |
243 | 244 |
244 const incompletePrefixRegexp = /[\s>+~]$/; | 245 const incompletePrefixRegexp = /[\s>+~]$/; |
245 | 246 |
246 function HasSelector(selectors) | 247 function HasSelector(selectors) |
247 { | 248 { |
248 this._innerSelectors = selectors; | 249 this._innerSelectors = selectors; |
249 } | 250 } |
250 | 251 |
251 HasSelector.prototype = { | 252 HasSelector.prototype = { |
252 requiresHiding: true, | |
253 dependsOnDOM: true, | 253 dependsOnDOM: true, |
254 | 254 |
255 get dependsOnStyles() | 255 get dependsOnStyles() |
256 { | 256 { |
257 return this._innerSelectors.some(selector => selector.dependsOnStyles); | 257 return this._innerSelectors.some(selector => selector.dependsOnStyles); |
258 }, | 258 }, |
259 | 259 |
260 get dependsOnCharacterData() | 260 get dependsOnCharacterData() |
261 { | 261 { |
262 return this._innerSelectors.some( | 262 return this._innerSelectors.some( |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
305 } | 305 } |
306 } | 306 } |
307 }; | 307 }; |
308 | 308 |
309 function ContainsSelector(textContent) | 309 function ContainsSelector(textContent) |
310 { | 310 { |
311 this._regexp = makeRegExpParameter(textContent); | 311 this._regexp = makeRegExpParameter(textContent); |
312 } | 312 } |
313 | 313 |
314 ContainsSelector.prototype = { | 314 ContainsSelector.prototype = { |
315 requiresHiding: true, | |
316 dependsOnDOM: true, | 315 dependsOnDOM: true, |
317 dependsOnCharacterData: true, | 316 dependsOnCharacterData: true, |
318 | 317 |
319 *getSelectors(prefix, subtree, styles) | 318 *getSelectors(prefix, subtree, styles) |
320 { | 319 { |
321 for (let element of this.getElements(prefix, subtree, styles)) | 320 for (let element of this.getElements(prefix, subtree, styles)) |
322 yield [makeSelector(element), subtree]; | 321 yield [makeSelector(element), subtree]; |
323 }, | 322 }, |
324 | 323 |
325 *getElements(prefix, subtree, styles) | 324 *getElements(prefix, subtree, styles) |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
363 regexpString = propertyExpression.slice(1, -1) | 362 regexpString = propertyExpression.slice(1, -1) |
364 .replace("\\7B ", "{").replace("\\7D ", "}"); | 363 .replace("\\7B ", "{").replace("\\7D ", "}"); |
365 } | 364 } |
366 else | 365 else |
367 regexpString = filterToRegExp(propertyExpression); | 366 regexpString = filterToRegExp(propertyExpression); |
368 | 367 |
369 this._regexp = new RegExp(regexpString, "i"); | 368 this._regexp = new RegExp(regexpString, "i"); |
370 } | 369 } |
371 | 370 |
372 PropsSelector.prototype = { | 371 PropsSelector.prototype = { |
373 preferHideWithSelector: true, | |
374 dependsOnStyles: true, | 372 dependsOnStyles: true, |
375 | 373 |
376 *findPropsSelectors(styles, prefix, regexp) | 374 *findPropsSelectors(styles, prefix, regexp) |
377 { | 375 { |
378 for (let style of styles) | 376 for (let style of styles) |
379 if (regexp.test(style.style)) | 377 if (regexp.test(style.style)) |
380 for (let subSelector of style.subSelectors) | 378 for (let subSelector of style.subSelectors) |
381 { | 379 { |
382 if (subSelector.startsWith("*") && | 380 if (subSelector.startsWith("*") && |
383 !incompletePrefixRegexp.test(prefix)) | 381 !incompletePrefixRegexp.test(prefix)) |
(...skipping 14 matching lines...) Expand all Loading... |
398 } | 396 } |
399 }; | 397 }; |
400 | 398 |
401 function Pattern(selectors, text) | 399 function Pattern(selectors, text) |
402 { | 400 { |
403 this.selectors = selectors; | 401 this.selectors = selectors; |
404 this.text = text; | 402 this.text = text; |
405 } | 403 } |
406 | 404 |
407 Pattern.prototype = { | 405 Pattern.prototype = { |
408 isSelectorHidingOnlyPattern() | |
409 { | |
410 return getCachedPropertyValue( | |
411 this, "_selectorHidingOnlyPattern", | |
412 () => this.selectors.some(selector => selector.preferHideWithSelector) && | |
413 !this.selectors.some(selector => selector.requiresHiding) | |
414 ); | |
415 }, | |
416 | |
417 get dependsOnStyles() | 406 get dependsOnStyles() |
418 { | 407 { |
419 return getCachedPropertyValue( | 408 return getCachedPropertyValue( |
420 this, "_dependsOnStyles", | 409 this, "_dependsOnStyles", |
421 () => this.selectors.some(selector => selector.dependsOnStyles) | 410 () => this.selectors.some(selector => selector.dependsOnStyles) |
422 ); | 411 ); |
423 }, | 412 }, |
424 | 413 |
425 get dependsOnDOM() | 414 get dependsOnDOM() |
426 { | 415 { |
(...skipping 30 matching lines...) Expand all Loading... |
457 }, | 446 }, |
458 | 447 |
459 get dependsOnCharacterData() | 448 get dependsOnCharacterData() |
460 { | 449 { |
461 // Observe changes to character data only if there's a contains selector in | 450 // Observe changes to character data only if there's a contains selector in |
462 // one of the patterns. | 451 // one of the patterns. |
463 return getCachedPropertyValue( | 452 return getCachedPropertyValue( |
464 this, "_dependsOnCharacterData", | 453 this, "_dependsOnCharacterData", |
465 () => this.selectors.some(selector => selector.dependsOnCharacterData) | 454 () => this.selectors.some(selector => selector.dependsOnCharacterData) |
466 ); | 455 ); |
| 456 }, |
| 457 |
| 458 matchesMutationTypes(mutationTypes) |
| 459 { |
| 460 let mutationTypeMatchMap = getCachedPropertyValue( |
| 461 this, "_mutationTypeMatchMap", |
| 462 () => new Map([ |
| 463 // All types of DOM-dependent patterns are affected by mutations of |
| 464 // type "childList". |
| 465 ["childList", true], |
| 466 ["attributes", this.maybeDependsOnAttributes], |
| 467 ["characterData", this.dependsOnCharacterData] |
| 468 ]) |
| 469 ); |
| 470 |
| 471 for (let mutationType of mutationTypes) |
| 472 { |
| 473 if (mutationTypeMatchMap.get(mutationType)) |
| 474 return true; |
| 475 } |
| 476 |
| 477 return false; |
467 } | 478 } |
468 }; | 479 }; |
| 480 |
| 481 function extractMutationTypes(mutations) |
| 482 { |
| 483 let types = new Set(); |
| 484 |
| 485 for (let mutation of mutations) |
| 486 { |
| 487 types.add(mutation.type); |
| 488 |
| 489 // There are only 3 types of mutations: "attributes", "characterData", and |
| 490 // "childList". |
| 491 if (types.size == 3) |
| 492 break; |
| 493 } |
| 494 |
| 495 return types; |
| 496 } |
469 | 497 |
470 function filterPatterns(patterns, {stylesheets, mutations}) | 498 function filterPatterns(patterns, {stylesheets, mutations}) |
471 { | 499 { |
472 if (!stylesheets && !mutations) | 500 if (!stylesheets && !mutations) |
473 return patterns.slice(); | 501 return patterns.slice(); |
474 | 502 |
| 503 let mutationTypes = mutations ? extractMutationTypes(mutations) : null; |
| 504 |
475 return patterns.filter( | 505 return patterns.filter( |
476 pattern => (stylesheets && pattern.dependsOnStyles) || | 506 pattern => (stylesheets && pattern.dependsOnStyles) || |
477 (mutations && pattern.dependsOnDOM) | 507 (mutations && pattern.dependsOnDOM && |
| 508 pattern.matchesMutationTypes(mutationTypes)) |
478 ); | 509 ); |
479 } | 510 } |
480 | 511 |
481 function shouldObserveAttributes(patterns) | 512 function shouldObserveAttributes(patterns) |
482 { | 513 { |
483 return patterns.some(pattern => pattern.maybeDependsOnAttributes); | 514 return patterns.some(pattern => pattern.maybeDependsOnAttributes); |
484 } | 515 } |
485 | 516 |
486 function shouldObserveCharacterData(patterns) | 517 function shouldObserveCharacterData(patterns) |
487 { | 518 { |
(...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
670 | 701 |
671 pattern = patterns.shift(); | 702 pattern = patterns.shift(); |
672 | 703 |
673 generator = evaluate(pattern.selectors, 0, "", | 704 generator = evaluate(pattern.selectors, 0, "", |
674 this.document, cssStyles); | 705 this.document, cssStyles); |
675 } | 706 } |
676 for (let selector of generator) | 707 for (let selector of generator) |
677 { | 708 { |
678 if (selector != null) | 709 if (selector != null) |
679 { | 710 { |
680 if (!this.useInlineStyles || | 711 if (!this.useInlineStyles) |
681 pattern.isSelectorHidingOnlyPattern()) | |
682 { | 712 { |
683 selectors.push(selector); | 713 selectors.push(selector); |
684 selectorFilters.push(pattern.text); | 714 selectorFilters.push(pattern.text); |
685 } | 715 } |
686 else | 716 else |
687 { | 717 { |
688 for (let element of this.document.querySelectorAll(selector)) | 718 for (let element of this.document.querySelectorAll(selector)) |
689 { | 719 { |
690 elements.push(element); | 720 elements.push(element); |
691 elementFilters.push(pattern.text); | 721 elementFilters.push(pattern.text); |
(...skipping 144 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
836 characterData: shouldObserveCharacterData(this.patterns), | 866 characterData: shouldObserveCharacterData(this.patterns), |
837 subtree: true | 867 subtree: true |
838 } | 868 } |
839 ); | 869 ); |
840 this.document.addEventListener("load", this.onLoad.bind(this), true); | 870 this.document.addEventListener("load", this.onLoad.bind(this), true); |
841 } | 871 } |
842 } | 872 } |
843 }; | 873 }; |
844 | 874 |
845 exports.ElemHideEmulation = ElemHideEmulation; | 875 exports.ElemHideEmulation = ElemHideEmulation; |
LEFT | RIGHT |