OLD | NEW |
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 176 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
187 try | 187 try |
188 { | 188 { |
189 return new RegExp(pattern, flags); | 189 return new RegExp(pattern, flags); |
190 } | 190 } |
191 catch (e) | 191 catch (e) |
192 { | 192 { |
193 } | 193 } |
194 return null; | 194 return null; |
195 } | 195 } |
196 | 196 |
197 function* evaluate(chain, index, prefix, subtree, styles) | 197 function* evaluate(chain, index, prefix, subtree, styles, targets) |
198 { | 198 { |
199 if (index >= chain.length) | 199 if (index >= chain.length) |
200 { | 200 { |
201 yield prefix; | 201 yield prefix; |
202 return; | 202 return; |
203 } | 203 } |
204 for (let [selector, element] of | 204 for (let [selector, element] of |
205 chain[index].getSelectors(prefix, subtree, styles)) | 205 chain[index].getSelectors(prefix, subtree, styles, targets)) |
206 { | 206 { |
207 if (selector == null) | 207 if (selector == null) |
208 yield null; | 208 yield null; |
209 else | 209 else |
210 yield* evaluate(chain, index + 1, selector, element, styles); | 210 yield* evaluate(chain, index + 1, selector, element, styles, targets); |
211 } | 211 } |
212 // Just in case the getSelectors() generator above had to run some heavy | 212 // Just in case the getSelectors() generator above had to run some heavy |
213 // document.querySelectorAll() call which didn't produce any results, make | 213 // document.querySelectorAll() call which didn't produce any results, make |
214 // sure there is at least one point where execution can pause. | 214 // sure there is at least one point where execution can pause. |
215 yield null; | 215 yield null; |
216 } | 216 } |
217 | 217 |
218 function PlainSelector(selector) | 218 function PlainSelector(selector) |
219 { | 219 { |
220 this._selector = selector; | 220 this._selector = selector; |
221 this.maybeDependsOnAttributes = /[#.]|\[.+\]/.test(selector); | 221 this.maybeDependsOnAttributes = /[#.]|\[.+\]/.test(selector); |
222 } | 222 } |
223 | 223 |
224 PlainSelector.prototype = { | 224 PlainSelector.prototype = { |
225 /** | 225 /** |
226 * Generator function returning a pair of selector | 226 * Generator function returning a pair of selector |
227 * string and subtree. | 227 * string and subtree. |
228 * @param {string} prefix the prefix for the selector. | 228 * @param {string} prefix the prefix for the selector. |
229 * @param {Node} subtree the subtree we work on. | 229 * @param {Node} subtree the subtree we work on. |
230 * @param {StringifiedStyle[]} styles the stringified style objects. | 230 * @param {StringifiedStyle[]} styles the stringified style objects. |
| 231 * @param {Node[]} [targets] the nodes we are interested in. |
231 */ | 232 */ |
232 *getSelectors(prefix, subtree, styles) | 233 *getSelectors(prefix, subtree, styles, targets) |
233 { | 234 { |
234 yield [prefix + this._selector, subtree]; | 235 yield [prefix + this._selector, subtree]; |
235 } | 236 } |
236 }; | 237 }; |
237 | 238 |
238 const incompletePrefixRegexp = /[\s>+~]$/; | 239 const incompletePrefixRegexp = /[\s>+~]$/; |
239 | 240 |
240 function HasSelector(selectors) | 241 function HasSelector(selectors) |
241 { | 242 { |
242 this._innerSelectors = selectors; | 243 this._innerSelectors = selectors; |
(...skipping 15 matching lines...) Expand all Loading... |
258 ); | 259 ); |
259 }, | 260 }, |
260 | 261 |
261 get maybeDependsOnAttributes() | 262 get maybeDependsOnAttributes() |
262 { | 263 { |
263 return this._innerSelectors.some( | 264 return this._innerSelectors.some( |
264 selector => selector.maybeDependsOnAttributes | 265 selector => selector.maybeDependsOnAttributes |
265 ); | 266 ); |
266 }, | 267 }, |
267 | 268 |
268 *getSelectors(prefix, subtree, styles) | 269 *getSelectors(prefix, subtree, styles, targets) |
269 { | 270 { |
270 for (let element of this.getElements(prefix, subtree, styles)) | 271 for (let element of this.getElements(prefix, subtree, styles, targets)) |
271 yield [makeSelector(element, ""), element]; | 272 yield [makeSelector(element, ""), element]; |
272 }, | 273 }, |
273 | 274 |
274 /** | 275 /** |
275 * Generator function returning selected elements. | 276 * Generator function returning selected elements. |
276 * @param {string} prefix the prefix for the selector. | 277 * @param {string} prefix the prefix for the selector. |
277 * @param {Node} subtree the subtree we work on. | 278 * @param {Node} subtree the subtree we work on. |
278 * @param {StringifiedStyle[]} styles the stringified style objects. | 279 * @param {StringifiedStyle[]} styles the stringified style objects. |
| 280 * @param {Node[]} [targets] the nodes we are interested in. |
279 */ | 281 */ |
280 *getElements(prefix, subtree, styles) | 282 *getElements(prefix, subtree, styles, targets) |
281 { | 283 { |
282 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? | 284 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? |
283 prefix + "*" : prefix; | 285 prefix + "*" : prefix; |
284 let elements = scopedQuerySelectorAll(subtree, actualPrefix); | 286 let elements = scopedQuerySelectorAll(subtree, actualPrefix); |
285 if (elements) | 287 if (elements) |
286 { | 288 { |
287 for (let element of elements) | 289 for (let element of elements) |
288 { | 290 { |
289 let iter = evaluate(this._innerSelectors, 0, "", element, styles); | 291 // If the element is neither an ancestor nor a descendant of one of the |
| 292 // targets, we can skip it. |
| 293 if (targets && !targets.some(target => element.contains(target) || |
| 294 target.contains(element))) |
| 295 { |
| 296 yield null; |
| 297 continue; |
| 298 } |
| 299 |
| 300 let iter = evaluate(this._innerSelectors, 0, "", element, styles, |
| 301 targets); |
290 for (let selector of iter) | 302 for (let selector of iter) |
291 { | 303 { |
292 if (selector == null) | 304 if (selector == null) |
293 yield null; | 305 yield null; |
294 else if (scopedQuerySelector(element, selector)) | 306 else if (scopedQuerySelector(element, selector)) |
295 yield element; | 307 yield element; |
296 } | 308 } |
297 yield null; | 309 yield null; |
298 } | 310 } |
299 } | 311 } |
300 } | 312 } |
301 }; | 313 }; |
302 | 314 |
303 function ContainsSelector(textContent) | 315 function ContainsSelector(textContent) |
304 { | 316 { |
305 this._regexp = makeRegExpParameter(textContent); | 317 this._regexp = makeRegExpParameter(textContent); |
306 } | 318 } |
307 | 319 |
308 ContainsSelector.prototype = { | 320 ContainsSelector.prototype = { |
309 requiresHiding: true, | 321 requiresHiding: true, |
310 dependsOnDOM: true, | 322 dependsOnDOM: true, |
311 dependsOnCharacterData: true, | 323 dependsOnCharacterData: true, |
312 | 324 |
313 *getSelectors(prefix, subtree, styles) | 325 *getSelectors(prefix, subtree, styles, targets) |
314 { | 326 { |
315 for (let element of this.getElements(prefix, subtree, styles)) | 327 for (let element of this.getElements(prefix, subtree, styles, targets)) |
316 yield [makeSelector(element, ""), subtree]; | 328 yield [makeSelector(element, ""), subtree]; |
317 }, | 329 }, |
318 | 330 |
319 *getElements(prefix, subtree, styles) | 331 *getElements(prefix, subtree, styles, targets) |
320 { | 332 { |
321 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? | 333 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? |
322 prefix + "*" : prefix; | 334 prefix + "*" : prefix; |
323 | 335 |
324 let elements = scopedQuerySelectorAll(subtree, actualPrefix); | 336 let elements = scopedQuerySelectorAll(subtree, actualPrefix); |
325 if (elements) | 337 if (elements) |
326 { | 338 { |
327 for (let element of elements) | 339 for (let element of elements) |
328 { | 340 { |
| 341 if (targets && !targets.some(target => element.contains(target) || |
| 342 target.contains(element))) |
| 343 { |
| 344 yield null; |
| 345 continue; |
| 346 } |
| 347 |
329 if (this._regexp && this._regexp.test(element.textContent)) | 348 if (this._regexp && this._regexp.test(element.textContent)) |
330 yield element; | 349 yield element; |
331 else | 350 else |
332 yield null; | 351 yield null; |
333 } | 352 } |
334 } | 353 } |
335 } | 354 } |
336 }; | 355 }; |
337 | 356 |
338 function PropsSelector(propertyExpression) | 357 function PropsSelector(propertyExpression) |
(...skipping 26 matching lines...) Expand all Loading... |
365 { | 384 { |
366 subSelector = subSelector.substr(1); | 385 subSelector = subSelector.substr(1); |
367 } | 386 } |
368 let idx = subSelector.lastIndexOf("::"); | 387 let idx = subSelector.lastIndexOf("::"); |
369 if (idx != -1) | 388 if (idx != -1) |
370 subSelector = subSelector.substr(0, idx); | 389 subSelector = subSelector.substr(0, idx); |
371 yield prefix + subSelector; | 390 yield prefix + subSelector; |
372 } | 391 } |
373 }, | 392 }, |
374 | 393 |
375 *getSelectors(prefix, subtree, styles) | 394 *getSelectors(prefix, subtree, styles, targets) |
376 { | 395 { |
377 for (let selector of this.findPropsSelectors(styles, prefix, this._regexp)) | 396 for (let selector of this.findPropsSelectors(styles, prefix, this._regexp)) |
378 yield [selector, subtree]; | 397 yield [selector, subtree]; |
379 } | 398 } |
380 }; | 399 }; |
381 | 400 |
382 function isSelectorHidingOnlyPattern(pattern) | 401 function isSelectorHidingOnlyPattern(pattern) |
383 { | 402 { |
384 return pattern.selectors.some(s => s.preferHideWithSelector) && | 403 return pattern.selectors.some(s => s.preferHideWithSelector) && |
385 !pattern.selectors.some(s => s.requiresHiding); | 404 !pattern.selectors.some(s => s.requiresHiding); |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
440 | 459 |
441 // There are only 3 types of mutations: "attributes", "characterData", and | 460 // There are only 3 types of mutations: "attributes", "characterData", and |
442 // "childList". | 461 // "childList". |
443 if (types.size == 3) | 462 if (types.size == 3) |
444 break; | 463 break; |
445 } | 464 } |
446 | 465 |
447 return types; | 466 return types; |
448 } | 467 } |
449 | 468 |
| 469 function extractMutationTargets(mutations) |
| 470 { |
| 471 if (!mutations) |
| 472 return null; |
| 473 |
| 474 let targets = new Set(); |
| 475 |
| 476 for (let mutation of mutations) |
| 477 { |
| 478 if (mutation.type == "childList") |
| 479 { |
| 480 // When new nodes are added, we're interested in the added nodes rather |
| 481 // than the parent. |
| 482 for (let node of mutation.addedNodes) |
| 483 targets.add(node); |
| 484 |
| 485 // Ideally we would also be interested in removed nodes, but since we |
| 486 // never unhide an element once hidden we can simply ignore any removed |
| 487 // nodes. Note that this will change once we start using CSS selectors |
| 488 // for -abp-has and -abp-contains, i.e. we'll have to remove the |
| 489 // selectors for any removed nodes. |
| 490 } |
| 491 else |
| 492 { |
| 493 targets.add(mutation.target); |
| 494 } |
| 495 } |
| 496 |
| 497 return [...targets]; |
| 498 } |
| 499 |
450 function filterPatterns(patterns, {stylesheets, mutations}) | 500 function filterPatterns(patterns, {stylesheets, mutations}) |
451 { | 501 { |
452 if (!stylesheets && !mutations) | 502 if (!stylesheets && !mutations) |
453 return patterns.slice(); | 503 return patterns.slice(); |
454 | 504 |
455 let mutationTypes = mutations ? extractMutationTypes(mutations) : null; | 505 let mutationTypes = mutations ? extractMutationTypes(mutations) : null; |
456 | 506 |
457 return patterns.filter( | 507 return patterns.filter( |
458 pattern => (stylesheets && patternDependsOnStyles(pattern)) || | 508 pattern => (stylesheets && patternDependsOnStyles(pattern)) || |
459 (mutations && patternDependsOnDOM(pattern) && | 509 (mutations && patternDependsOnDOM(pattern) && |
(...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
610 | 660 |
611 for (let rule of rules) | 661 for (let rule of rules) |
612 { | 662 { |
613 if (rule.type != rule.STYLE_RULE) | 663 if (rule.type != rule.STYLE_RULE) |
614 continue; | 664 continue; |
615 | 665 |
616 cssStyles.push(stringifyStyle(rule)); | 666 cssStyles.push(stringifyStyle(rule)); |
617 } | 667 } |
618 } | 668 } |
619 | 669 |
| 670 let targets = extractMutationTargets(mutations); |
| 671 |
620 let pattern = null; | 672 let pattern = null; |
621 let generator = null; | 673 let generator = null; |
622 | 674 |
623 let processPatterns = () => | 675 let processPatterns = () => |
624 { | 676 { |
625 let cycleStart = performance.now(); | 677 let cycleStart = performance.now(); |
626 | 678 |
627 if (!pattern) | 679 if (!pattern) |
628 { | 680 { |
629 if (!patterns.length) | 681 if (!patterns.length) |
630 { | 682 { |
631 this.addSelectorsFunc(selectors, selectorFilters); | 683 this.addSelectorsFunc(selectors, selectorFilters); |
632 this.hideElemsFunc(elements, elementFilters); | 684 this.hideElemsFunc(elements, elementFilters); |
633 if (typeof done == "function") | 685 if (typeof done == "function") |
634 done(); | 686 done(); |
635 return; | 687 return; |
636 } | 688 } |
637 | 689 |
638 pattern = patterns.shift(); | 690 pattern = patterns.shift(); |
639 | 691 |
640 generator = evaluate(pattern.selectors, 0, "", | 692 generator = evaluate(pattern.selectors, 0, "", |
641 this.document, cssStyles); | 693 this.document, cssStyles, targets); |
642 } | 694 } |
643 for (let selector of generator) | 695 for (let selector of generator) |
644 { | 696 { |
645 if (selector != null) | 697 if (selector != null) |
646 { | 698 { |
647 if (isSelectorHidingOnlyPattern(pattern)) | 699 if (isSelectorHidingOnlyPattern(pattern)) |
648 { | 700 { |
649 selectors.push(selector); | 701 selectors.push(selector); |
650 selectorFilters.push(pattern.text); | 702 selectorFilters.push(pattern.text); |
651 } | 703 } |
(...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
801 characterData: shouldObserveCharacterData(this.patterns), | 853 characterData: shouldObserveCharacterData(this.patterns), |
802 subtree: true | 854 subtree: true |
803 } | 855 } |
804 ); | 856 ); |
805 this.document.addEventListener("load", this.onLoad.bind(this), true); | 857 this.document.addEventListener("load", this.onLoad.bind(this), true); |
806 } | 858 } |
807 } | 859 } |
808 }; | 860 }; |
809 | 861 |
810 exports.ElemHideEmulation = ElemHideEmulation; | 862 exports.ElemHideEmulation = ElemHideEmulation; |
OLD | NEW |