| 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 |