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

Side by Side Diff: lib/content/elemHideEmulation.js

Issue 29714601: Issue 6437 - Skip elements not affected by DOM mutations (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore/
Patch Set: Disable optimization for patterns containing sibling combinators Created May 11, 2018, 4:18 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 181 matching lines...) Expand 10 before | Expand all | Expand 10 after
192 try 192 try
193 { 193 {
194 return new RegExp(pattern, flags); 194 return new RegExp(pattern, flags);
195 } 195 }
196 catch (e) 196 catch (e)
197 { 197 {
198 } 198 }
199 return null; 199 return null;
200 } 200 }
201 201
202 function* evaluate(chain, index, prefix, subtree, styles) 202 function* evaluate(chain, index, prefix, subtree, styles, targets)
203 { 203 {
204 if (index >= chain.length) 204 if (index >= chain.length)
205 { 205 {
206 yield prefix; 206 yield prefix;
207 return; 207 return;
208 } 208 }
209 for (let [selector, element] of 209 for (let [selector, element] of
210 chain[index].getSelectors(prefix, subtree, styles)) 210 chain[index].getSelectors(prefix, subtree, styles, targets))
211 { 211 {
212 if (selector == null) 212 if (selector == null)
213 yield null; 213 yield null;
214 else 214 else
215 yield* evaluate(chain, index + 1, selector, element, styles); 215 yield* evaluate(chain, index + 1, selector, element, styles, targets);
216 } 216 }
217 // Just in case the getSelectors() generator above had to run some heavy 217 // Just in case the getSelectors() generator above had to run some heavy
218 // document.querySelectorAll() call which didn't produce any results, make 218 // document.querySelectorAll() call which didn't produce any results, make
219 // sure there is at least one point where execution can pause. 219 // sure there is at least one point where execution can pause.
220 yield null; 220 yield null;
221 } 221 }
222 222
223 function PlainSelector(selector) 223 function PlainSelector(selector)
224 { 224 {
225 this._selector = selector; 225 this._selector = selector;
226 this.maybeDependsOnAttributes = /[#.]|\[.+\]/.test(selector); 226 this.maybeDependsOnAttributes = /[#.]|\[.+\]/.test(selector);
227 this.dependsOnDOM = this.maybeDependsOnAttributes; 227 this.dependsOnDOM = this.maybeDependsOnAttributes;
228 this.maybeContainsSiblingCombinators = /[~+]/.test(selector);
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.
238 * @param {Node[]} [targets] the nodes we are interested in.
237 */ 239 */
238 *getSelectors(prefix, subtree, styles) 240 *getSelectors(prefix, subtree, styles, targets)
239 { 241 {
240 yield [prefix + this._selector, subtree]; 242 yield [prefix + this._selector, subtree];
241 } 243 }
242 }; 244 };
243 245
244 const incompletePrefixRegexp = /[\s>+~]$/; 246 const incompletePrefixRegexp = /[\s>+~]$/;
245 247
246 function HasSelector(selectors) 248 function HasSelector(selectors)
247 { 249 {
248 this._innerSelectors = selectors; 250 this._innerSelectors = selectors;
(...skipping 14 matching lines...) Expand all
263 ); 265 );
264 }, 266 },
265 267
266 get maybeDependsOnAttributes() 268 get maybeDependsOnAttributes()
267 { 269 {
268 return this._innerSelectors.some( 270 return this._innerSelectors.some(
269 selector => selector.maybeDependsOnAttributes 271 selector => selector.maybeDependsOnAttributes
270 ); 272 );
271 }, 273 },
272 274
273 *getSelectors(prefix, subtree, styles) 275 *getSelectors(prefix, subtree, styles, targets)
274 { 276 {
275 for (let element of this.getElements(prefix, subtree, styles)) 277 for (let element of this.getElements(prefix, subtree, styles, targets))
276 yield [makeSelector(element), element]; 278 yield [makeSelector(element), element];
277 }, 279 },
278 280
279 /** 281 /**
280 * Generator function returning selected elements. 282 * Generator function returning selected elements.
281 * @param {string} prefix the prefix for the selector. 283 * @param {string} prefix the prefix for the selector.
282 * @param {Node} subtree the subtree we work on. 284 * @param {Node} subtree the subtree we work on.
283 * @param {StringifiedStyle[]} styles the stringified style objects. 285 * @param {StringifiedStyle[]} styles the stringified style objects.
286 * @param {Node[]} [targets] the nodes we are interested in.
284 */ 287 */
285 *getElements(prefix, subtree, styles) 288 *getElements(prefix, subtree, styles, targets)
286 { 289 {
287 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? 290 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ?
288 prefix + "*" : prefix; 291 prefix + "*" : prefix;
289 let elements = scopedQuerySelectorAll(subtree, actualPrefix); 292 let elements = scopedQuerySelectorAll(subtree, actualPrefix);
290 if (elements) 293 if (elements)
291 { 294 {
292 for (let element of elements) 295 for (let element of elements)
293 { 296 {
294 let iter = evaluate(this._innerSelectors, 0, "", element, styles); 297 // If the element is neither an ancestor nor a descendant of one of the
298 // targets, we can skip it.
299 if (targets && !targets.some(target => element.contains(target) ||
300 target.contains(element)))
301 {
302 yield null;
303 continue;
304 }
305
306 let iter = evaluate(this._innerSelectors, 0, "", element, styles,
307 targets);
295 for (let selector of iter) 308 for (let selector of iter)
296 { 309 {
297 if (selector == null) 310 if (selector == null)
298 yield null; 311 yield null;
299 else if (scopedQuerySelector(element, selector)) 312 else if (scopedQuerySelector(element, selector))
300 yield element; 313 yield element;
301 } 314 }
302 yield null; 315 yield null;
303 } 316 }
304 } 317 }
305 } 318 }
306 }; 319 };
307 320
308 function ContainsSelector(textContent) 321 function ContainsSelector(textContent)
309 { 322 {
310 this._regexp = makeRegExpParameter(textContent); 323 this._regexp = makeRegExpParameter(textContent);
311 } 324 }
312 325
313 ContainsSelector.prototype = { 326 ContainsSelector.prototype = {
314 dependsOnDOM: true, 327 dependsOnDOM: true,
315 dependsOnCharacterData: true, 328 dependsOnCharacterData: true,
316 329
317 *getSelectors(prefix, subtree, styles) 330 *getSelectors(prefix, subtree, styles, targets)
318 { 331 {
319 for (let element of this.getElements(prefix, subtree, styles)) 332 for (let element of this.getElements(prefix, subtree, styles, targets))
320 yield [makeSelector(element), subtree]; 333 yield [makeSelector(element), subtree];
321 }, 334 },
322 335
323 *getElements(prefix, subtree, styles) 336 *getElements(prefix, subtree, styles, targets)
324 { 337 {
325 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? 338 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ?
326 prefix + "*" : prefix; 339 prefix + "*" : prefix;
327 340
328 let elements = scopedQuerySelectorAll(subtree, actualPrefix); 341 let elements = scopedQuerySelectorAll(subtree, actualPrefix);
329 342
330 if (elements) 343 if (elements)
331 { 344 {
332 let lastRoot = null; 345 let lastRoot = null;
333 for (let element of elements) 346 for (let element of elements)
334 { 347 {
335 // For a filter like div:-abp-contains(Hello) and a subtree like 348 // For a filter like div:-abp-contains(Hello) and a subtree like
336 // <div id="a"><div id="b"><div id="c">Hello</div></div></div> 349 // <div id="a"><div id="b"><div id="c">Hello</div></div></div>
337 // we're only interested in div#a 350 // we're only interested in div#a
338 if (lastRoot && lastRoot.contains(element)) 351 if (lastRoot && lastRoot.contains(element))
339 { 352 {
340 yield null; 353 yield null;
341 continue; 354 continue;
342 } 355 }
343 356
344 lastRoot = element; 357 lastRoot = element;
345 358
359 if (targets && !targets.some(target => element.contains(target) ||
360 target.contains(element)))
361 {
362 yield null;
363 continue;
364 }
365
346 if (this._regexp && this._regexp.test(element.textContent)) 366 if (this._regexp && this._regexp.test(element.textContent))
347 yield element; 367 yield element;
348 else 368 else
349 yield null; 369 yield null;
350 } 370 }
351 } 371 }
352 } 372 }
353 }; 373 };
354 374
355 function PropsSelector(propertyExpression) 375 function PropsSelector(propertyExpression)
(...skipping 25 matching lines...) Expand all
381 { 401 {
382 subSelector = subSelector.substr(1); 402 subSelector = subSelector.substr(1);
383 } 403 }
384 let idx = subSelector.lastIndexOf("::"); 404 let idx = subSelector.lastIndexOf("::");
385 if (idx != -1) 405 if (idx != -1)
386 subSelector = subSelector.substr(0, idx); 406 subSelector = subSelector.substr(0, idx);
387 yield prefix + subSelector; 407 yield prefix + subSelector;
388 } 408 }
389 }, 409 },
390 410
391 *getSelectors(prefix, subtree, styles) 411 *getSelectors(prefix, subtree, styles, targets)
392 { 412 {
393 for (let selector of this.findPropsSelectors(styles, prefix, this._regexp)) 413 for (let selector of this.findPropsSelectors(styles, prefix, this._regexp))
394 yield [selector, subtree]; 414 yield [selector, subtree];
395 } 415 }
396 }; 416 };
397 417
398 function Pattern(selectors, text) 418 function Pattern(selectors, text)
399 { 419 {
400 this.selectors = selectors; 420 this.selectors = selectors;
401 this.text = text; 421 this.text = text;
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
447 get dependsOnCharacterData() 467 get dependsOnCharacterData()
448 { 468 {
449 // Observe changes to character data only if there's a contains selector in 469 // Observe changes to character data only if there's a contains selector in
450 // one of the patterns. 470 // one of the patterns.
451 return getCachedPropertyValue( 471 return getCachedPropertyValue(
452 this, "_dependsOnCharacterData", 472 this, "_dependsOnCharacterData",
453 () => this.selectors.some(selector => selector.dependsOnCharacterData) 473 () => this.selectors.some(selector => selector.dependsOnCharacterData)
454 ); 474 );
455 }, 475 },
456 476
477 get maybeContainsSiblingCombinators()
478 {
479 return getCachedPropertyValue(
480 this, "_maybeContainsSiblingCombinators",
481 () => this.selectors.some(selector =>
482 selector.maybeContainsSiblingCombinators)
483 );
484 },
485
457 matchesMutationTypes(mutationTypes) 486 matchesMutationTypes(mutationTypes)
458 { 487 {
459 let mutationTypeMatchMap = getCachedPropertyValue( 488 let mutationTypeMatchMap = getCachedPropertyValue(
460 this, "_mutationTypeMatchMap", 489 this, "_mutationTypeMatchMap",
461 () => new Map([ 490 () => new Map([
462 // All types of DOM-dependent patterns are affected by mutations of 491 // All types of DOM-dependent patterns are affected by mutations of
463 // type "childList". 492 // type "childList".
464 ["childList", true], 493 ["childList", true],
465 ["attributes", this.maybeDependsOnAttributes], 494 ["attributes", this.maybeDependsOnAttributes],
466 ["characterData", this.dependsOnCharacterData] 495 ["characterData", this.dependsOnCharacterData]
(...skipping 20 matching lines...) Expand all
487 516
488 // There are only 3 types of mutations: "attributes", "characterData", and 517 // There are only 3 types of mutations: "attributes", "characterData", and
489 // "childList". 518 // "childList".
490 if (types.size == 3) 519 if (types.size == 3)
491 break; 520 break;
492 } 521 }
493 522
494 return types; 523 return types;
495 } 524 }
496 525
526 function extractMutationTargets(mutations)
527 {
528 if (!mutations)
529 return null;
530
531 let targets = new Set();
532
533 for (let mutation of mutations)
534 {
535 if (mutation.type == "childList")
536 {
537 // When new nodes are added, we're interested in the added nodes rather
538 // than the parent.
539 for (let node of mutation.addedNodes)
540 targets.add(node);
541
542 // Ideally we would also be interested in removed nodes, but since we
Manish Jethani 2018/05/11 16:32:00 Well we're already using CSS selectors for element
543 // never unhide an element once hidden we can simply ignore any removed
544 // nodes. Note that this will change once we start using CSS selectors
545 // for -abp-has and -abp-contains, i.e. we'll have to remove the
546 // selectors for any removed nodes.
547 }
548 else
549 {
550 targets.add(mutation.target);
551 }
552 }
553
554 return [...targets];
555 }
556
497 function filterPatterns(patterns, {stylesheets, mutations}) 557 function filterPatterns(patterns, {stylesheets, mutations})
498 { 558 {
499 if (!stylesheets && !mutations) 559 if (!stylesheets && !mutations)
500 return patterns.slice(); 560 return patterns.slice();
501 561
502 let mutationTypes = mutations ? extractMutationTypes(mutations) : null; 562 let mutationTypes = mutations ? extractMutationTypes(mutations) : null;
503 563
504 return patterns.filter( 564 return patterns.filter(
505 pattern => (stylesheets && pattern.dependsOnStyles) || 565 pattern => (stylesheets && pattern.dependsOnStyles) ||
506 (mutations && pattern.dependsOnDOM && 566 (mutations && pattern.dependsOnDOM &&
(...skipping 164 matching lines...) Expand 10 before | Expand all | Expand 10 after
671 731
672 for (let rule of rules) 732 for (let rule of rules)
673 { 733 {
674 if (rule.type != rule.STYLE_RULE) 734 if (rule.type != rule.STYLE_RULE)
675 continue; 735 continue;
676 736
677 cssStyles.push(stringifyStyle(rule)); 737 cssStyles.push(stringifyStyle(rule));
678 } 738 }
679 } 739 }
680 740
741 let targets = extractMutationTargets(mutations);
742
681 let pattern = null; 743 let pattern = null;
682 let generator = null; 744 let generator = null;
683 745
684 let processPatterns = () => 746 let processPatterns = () =>
685 { 747 {
686 let cycleStart = performance.now(); 748 let cycleStart = performance.now();
687 749
688 if (!pattern) 750 if (!pattern)
689 { 751 {
690 if (!patterns.length) 752 if (!patterns.length)
691 { 753 {
692 if (selectors.length > 0) 754 if (selectors.length > 0)
693 this.addSelectorsFunc(selectors, selectorFilters); 755 this.addSelectorsFunc(selectors, selectorFilters);
694 if (elements.length > 0) 756 if (elements.length > 0)
695 this.hideElemsFunc(elements, elementFilters); 757 this.hideElemsFunc(elements, elementFilters);
696 if (typeof done == "function") 758 if (typeof done == "function")
697 done(); 759 done();
698 return; 760 return;
699 } 761 }
700 762
701 pattern = patterns.shift(); 763 pattern = patterns.shift();
702 764
765 let evaluationTargets = targets;
766
767 // If the pattern appears to contain any sibling combinators, we can't
768 // easily optimize based on the mutation targets. Since this is a
769 // special case, skip the optimization. By setting it to null here we
770 // make sure we process the entire DOM.
771 if (pattern.maybeContainsSiblingCombinators)
772 evaluationTargets = null;
773
703 generator = evaluate(pattern.selectors, 0, "", 774 generator = evaluate(pattern.selectors, 0, "",
704 this.document, cssStyles); 775 this.document, cssStyles, evaluationTargets);
705 } 776 }
706 for (let selector of generator) 777 for (let selector of generator)
707 { 778 {
708 if (selector != null) 779 if (selector != null)
709 { 780 {
710 if (!this.useInlineStyles) 781 if (!this.useInlineStyles)
711 { 782 {
712 selectors.push(selector); 783 selectors.push(selector);
713 selectorFilters.push(pattern.text); 784 selectorFilters.push(pattern.text);
714 } 785 }
(...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after
865 characterData: shouldObserveCharacterData(this.patterns), 936 characterData: shouldObserveCharacterData(this.patterns),
866 subtree: true 937 subtree: true
867 } 938 }
868 ); 939 );
869 this.document.addEventListener("load", this.onLoad.bind(this), true); 940 this.document.addEventListener("load", this.onLoad.bind(this), true);
870 } 941 }
871 } 942 }
872 }; 943 };
873 944
874 exports.ElemHideEmulation = ElemHideEmulation; 945 exports.ElemHideEmulation = ElemHideEmulation;
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld