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

Delta Between Two Patch Sets: lib/content/elemHideEmulation.js

Issue 29714601: Issue 6437 - Skip elements not affected by DOM mutations (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore/
Left Patch Set: Rebase Created March 20, 2018, 10:04 a.m.
Right Patch Set: Rename debug mode to test mode Created May 18, 2018, 3:41 a.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 | « no previous file | test/browser/elemHideEmulation.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-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
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details. 12 * GNU General Public License for more details.
13 * 13 *
14 * You should have received a copy of the GNU General Public License 14 * You should have received a copy of the GNU General Public License
15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
16 */ 16 */
17 17
18 "use strict"; 18 "use strict";
19 19
20 const {textToRegExp, filterToRegExp, splitSelector} = require("../common"); 20 const {textToRegExp, filterToRegExp, splitSelector} = require("../common");
21 const {indexOf} = require("../coreUtils");
21 22
22 let MIN_INVOCATION_INTERVAL = 3000; 23 let MIN_INVOCATION_INTERVAL = 3000;
23 const MAX_SYNCHRONOUS_PROCESSING_TIME = 50; 24 const MAX_SYNCHRONOUS_PROCESSING_TIME = 50;
24 const abpSelectorRegexp = /:-abp-([\w-]+)\(/i; 25 const abpSelectorRegexp = /:-abp-([\w-]+)\(/i;
26
27 let testInfo = null;
28
29 function setTestMode()
30 {
31 testInfo = {
32 lastProcessedElements: new Set()
33 };
34 }
35
36 exports.setTestMode = setTestMode;
37
38 function getTestInfo()
39 {
40 return testInfo;
41 }
42
43 exports.getTestInfo = getTestInfo;
hub 2018/05/24 19:19:09 I'm a bit skeptical with that way or test, includi
Manish Jethani 2018/05/25 07:21:25 We can work on improving our test framework, but f
25 44
26 function getCachedPropertyValue(object, name, defaultValueFunc = () => {}) 45 function getCachedPropertyValue(object, name, defaultValueFunc = () => {})
27 { 46 {
28 let value = object[name]; 47 let value = object[name];
29 if (typeof value == "undefined") 48 if (typeof value == "undefined")
30 Object.defineProperty(object, name, {value: value = defaultValueFunc()}); 49 Object.defineProperty(object, name, {value: value = defaultValueFunc()});
31 return value; 50 return value;
32 } 51 }
33 52
34 /** Return position of node from parent. 53 /** Return position of node from parent.
35 * @param {Node} node the node to find the position of. 54 * @param {Node} node the node to find the position of.
36 * @return {number} One-based index like for :nth-child(), or 0 on error. 55 * @return {number} One-based index like for :nth-child(), or 0 on error.
37 */ 56 */
38 function positionInParent(node) 57 function positionInParent(node)
39 { 58 {
40 let {children} = node.parentNode; 59 return indexOf(node.parentNode.children, node) + 1;
41 for (let i = 0; i < children.length; i++) 60 }
42 if (children[i] == node) 61
43 return i + 1; 62 function makeSelector(node, selector = "")
44 return 0;
45 }
46
47 function makeSelector(node, selector)
48 { 63 {
49 if (node == null) 64 if (node == null)
50 return null; 65 return null;
51 if (!node.parentElement) 66 if (!node.parentElement)
52 { 67 {
53 let newSelector = ":root"; 68 let newSelector = ":root";
54 if (selector) 69 if (selector)
55 newSelector += " > " + selector; 70 newSelector += " > " + selector;
56 return newSelector; 71 return newSelector;
57 } 72 }
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after
172 } 187 }
173 return all ? subtree.querySelectorAll(selector) : 188 return all ? subtree.querySelectorAll(selector) :
174 subtree.querySelector(selector); 189 subtree.querySelector(selector);
175 } 190 }
176 191
177 function scopedQuerySelectorAll(subtree, selector) 192 function scopedQuerySelectorAll(subtree, selector)
178 { 193 {
179 return scopedQuerySelector(subtree, selector, true); 194 return scopedQuerySelector(subtree, selector, true);
180 } 195 }
181 196
182 const regexpRegexp = /^\/(.*)\/([im]*)$/; 197 const regexpRegexp = /^\/(.*)\/([imu]*)$/;
183 198
184 /** 199 /**
185 * Make a regular expression from a text argument. If it can be parsed as a 200 * Make a regular expression from a text argument. If it can be parsed as a
186 * regular expression, parse it and the flags. 201 * regular expression, parse it and the flags.
187 * @param {string} text the text argument. 202 * @param {string} text the text argument.
188 * @return {?RegExp} a RegExp object or null in case of error. 203 * @return {?RegExp} a RegExp object or null in case of error.
189 */ 204 */
190 function makeRegExpParameter(text) 205 function makeRegExpParameter(text)
191 { 206 {
192 let [, pattern, flags] = 207 let [, pattern, flags] =
(...skipping 27 matching lines...) Expand all
220 // Just in case the getSelectors() generator above had to run some heavy 235 // Just in case the getSelectors() generator above had to run some heavy
221 // document.querySelectorAll() call which didn't produce any results, make 236 // document.querySelectorAll() call which didn't produce any results, make
222 // sure there is at least one point where execution can pause. 237 // sure there is at least one point where execution can pause.
223 yield null; 238 yield null;
224 } 239 }
225 240
226 function PlainSelector(selector) 241 function PlainSelector(selector)
227 { 242 {
228 this._selector = selector; 243 this._selector = selector;
229 this.maybeDependsOnAttributes = /[#.]|\[.+\]/.test(selector); 244 this.maybeDependsOnAttributes = /[#.]|\[.+\]/.test(selector);
245 this.dependsOnDOM = this.maybeDependsOnAttributes;
246 this.maybeContainsSiblingCombinators = /[~+]/.test(selector);
230 } 247 }
231 248
232 PlainSelector.prototype = { 249 PlainSelector.prototype = {
233 /** 250 /**
234 * Generator function returning a pair of selector 251 * Generator function returning a pair of selector
235 * string and subtree. 252 * string and subtree.
236 * @param {string} prefix the prefix for the selector. 253 * @param {string} prefix the prefix for the selector.
237 * @param {Node} subtree the subtree we work on. 254 * @param {Node} subtree the subtree we work on.
238 * @param {StringifiedStyle[]} styles the stringified style objects. 255 * @param {StringifiedStyle[]} styles the stringified style objects.
239 * @param {Node[]} [targets] the nodes we are interested in. 256 * @param {Node[]} [targets] the nodes we are interested in.
240 */ 257 */
241 *getSelectors(prefix, subtree, styles, targets) 258 *getSelectors(prefix, subtree, styles, targets)
242 { 259 {
243 yield [prefix + this._selector, subtree]; 260 yield [prefix + this._selector, subtree];
244 } 261 }
245 }; 262 };
246 263
247 const incompletePrefixRegexp = /[\s>+~]$/; 264 const incompletePrefixRegexp = /[\s>+~]$/;
248 265
249 function HasSelector(selectors) 266 function HasSelector(selectors)
250 { 267 {
251 this._innerSelectors = selectors; 268 this._innerSelectors = selectors;
252 } 269 }
253 270
254 HasSelector.prototype = { 271 HasSelector.prototype = {
255 requiresHiding: true,
256 dependsOnDOM: true, 272 dependsOnDOM: true,
257 273
258 get dependsOnStyles() 274 get dependsOnStyles()
259 { 275 {
260 return this._innerSelectors.some(selector => selector.dependsOnStyles); 276 return this._innerSelectors.some(selector => selector.dependsOnStyles);
261 }, 277 },
262 278
263 get dependsOnCharacterData() 279 get dependsOnCharacterData()
264 { 280 {
265 return this._innerSelectors.some( 281 return this._innerSelectors.some(
266 selector => selector.dependsOnCharacterData 282 selector => selector.dependsOnCharacterData
267 ); 283 );
268 }, 284 },
269 285
270 get maybeDependsOnAttributes() 286 get maybeDependsOnAttributes()
271 { 287 {
272 return this._innerSelectors.some( 288 return this._innerSelectors.some(
273 selector => selector.maybeDependsOnAttributes 289 selector => selector.maybeDependsOnAttributes
274 ); 290 );
275 }, 291 },
276 292
277 *getSelectors(prefix, subtree, styles, targets) 293 *getSelectors(prefix, subtree, styles, targets)
278 { 294 {
279 for (let element of this.getElements(prefix, subtree, styles, targets)) 295 for (let element of this.getElements(prefix, subtree, styles, targets))
280 yield [makeSelector(element, ""), element]; 296 yield [makeSelector(element), element];
281 }, 297 },
282 298
283 /** 299 /**
284 * Generator function returning selected elements. 300 * Generator function returning selected elements.
285 * @param {string} prefix the prefix for the selector. 301 * @param {string} prefix the prefix for the selector.
286 * @param {Node} subtree the subtree we work on. 302 * @param {Node} subtree the subtree we work on.
287 * @param {StringifiedStyle[]} styles the stringified style objects. 303 * @param {StringifiedStyle[]} styles the stringified style objects.
288 * @param {Node[]} [targets] the nodes we are interested in. 304 * @param {Node[]} [targets] the nodes we are interested in.
289 */ 305 */
290 *getElements(prefix, subtree, styles, targets) 306 *getElements(prefix, subtree, styles, targets)
(...skipping 17 matching lines...) Expand all
308 let iter = evaluate(this._innerSelectors, 0, "", element, styles, 324 let iter = evaluate(this._innerSelectors, 0, "", element, styles,
309 targets); 325 targets);
310 for (let selector of iter) 326 for (let selector of iter)
311 { 327 {
312 if (selector == null) 328 if (selector == null)
313 yield null; 329 yield null;
314 else if (scopedQuerySelector(element, selector)) 330 else if (scopedQuerySelector(element, selector))
315 yield element; 331 yield element;
316 } 332 }
317 yield null; 333 yield null;
334
335 if (testInfo)
336 testInfo.lastProcessedElements.add(element);
318 } 337 }
319 } 338 }
320 } 339 }
321 }; 340 };
322 341
323 function ContainsSelector(textContent) 342 function ContainsSelector(textContent)
324 { 343 {
325 this._regexp = makeRegExpParameter(textContent); 344 this._regexp = makeRegExpParameter(textContent);
326 } 345 }
327 346
328 ContainsSelector.prototype = { 347 ContainsSelector.prototype = {
329 requiresHiding: true,
330 dependsOnDOM: true, 348 dependsOnDOM: true,
331 dependsOnCharacterData: true, 349 dependsOnCharacterData: true,
332 350
333 *getSelectors(prefix, subtree, styles, targets) 351 *getSelectors(prefix, subtree, styles, targets)
334 { 352 {
335 for (let element of this.getElements(prefix, subtree, styles, targets)) 353 for (let element of this.getElements(prefix, subtree, styles, targets))
336 yield [makeSelector(element, ""), subtree]; 354 yield [makeSelector(element), subtree];
337 }, 355 },
338 356
339 *getElements(prefix, subtree, styles, targets) 357 *getElements(prefix, subtree, styles, targets)
340 { 358 {
341 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? 359 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ?
342 prefix + "*" : prefix; 360 prefix + "*" : prefix;
343 361
344 let elements = scopedQuerySelectorAll(subtree, actualPrefix); 362 let elements = scopedQuerySelectorAll(subtree, actualPrefix);
363
345 if (elements) 364 if (elements)
346 { 365 {
366 let lastRoot = null;
347 for (let element of elements) 367 for (let element of elements)
348 { 368 {
369 // For a filter like div:-abp-contains(Hello) and a subtree like
370 // <div id="a"><div id="b"><div id="c">Hello</div></div></div>
371 // we're only interested in div#a
372 if (lastRoot && lastRoot.contains(element))
373 {
374 yield null;
375 continue;
376 }
377
378 lastRoot = element;
379
349 if (targets && !targets.some(target => element.contains(target) || 380 if (targets && !targets.some(target => element.contains(target) ||
Manish Jethani 2018/03/20 10:21:57 By the way this may not seem like such a great ide
350 target.contains(element))) 381 target.contains(element)))
351 { 382 {
352 yield null; 383 yield null;
353 continue; 384 continue;
354 } 385 }
355 386
356 if (this._regexp && this._regexp.test(element.textContent)) 387 if (this._regexp && this._regexp.test(element.textContent))
357 yield element; 388 yield element;
358 else 389 else
359 yield null; 390 yield null;
391
392 if (testInfo)
393 testInfo.lastProcessedElements.add(element);
360 } 394 }
361 } 395 }
362 } 396 }
363 }; 397 };
364 398
365 function PropsSelector(propertyExpression) 399 function PropsSelector(propertyExpression)
366 { 400 {
367 let regexpString; 401 let regexpString;
368 if (propertyExpression.length >= 2 && propertyExpression[0] == "/" && 402 if (propertyExpression.length >= 2 && propertyExpression[0] == "/" &&
369 propertyExpression[propertyExpression.length - 1] == "/") 403 propertyExpression[propertyExpression.length - 1] == "/")
370 { 404 {
371 regexpString = propertyExpression.slice(1, -1) 405 regexpString = propertyExpression.slice(1, -1)
372 .replace("\\7B ", "{").replace("\\7D ", "}"); 406 .replace("\\7B ", "{").replace("\\7D ", "}");
373 } 407 }
374 else 408 else
375 regexpString = filterToRegExp(propertyExpression); 409 regexpString = filterToRegExp(propertyExpression);
376 410
377 this._regexp = new RegExp(regexpString, "i"); 411 this._regexp = new RegExp(regexpString, "i");
378 } 412 }
379 413
380 PropsSelector.prototype = { 414 PropsSelector.prototype = {
381 preferHideWithSelector: true,
382 dependsOnStyles: true, 415 dependsOnStyles: true,
383 416
384 *findPropsSelectors(styles, prefix, regexp) 417 *findPropsSelectors(styles, prefix, regexp)
385 { 418 {
386 for (let style of styles) 419 for (let style of styles)
387 if (regexp.test(style.style)) 420 if (regexp.test(style.style))
388 for (let subSelector of style.subSelectors) 421 for (let subSelector of style.subSelectors)
389 { 422 {
390 if (subSelector.startsWith("*") && 423 if (subSelector.startsWith("*") &&
391 !incompletePrefixRegexp.test(prefix)) 424 !incompletePrefixRegexp.test(prefix))
(...skipping 14 matching lines...) Expand all
406 } 439 }
407 }; 440 };
408 441
409 function Pattern(selectors, text) 442 function Pattern(selectors, text)
410 { 443 {
411 this.selectors = selectors; 444 this.selectors = selectors;
412 this.text = text; 445 this.text = text;
413 } 446 }
414 447
415 Pattern.prototype = { 448 Pattern.prototype = {
416 isSelectorHidingOnlyPattern()
417 {
418 return getCachedPropertyValue(
419 this, "_selectorHidingOnlyPattern",
420 () => this.selectors.some(selector => selector.preferHideWithSelector) &&
421 !this.selectors.some(selector => selector.requiresHiding)
422 );
423 },
424
425 get dependsOnStyles() 449 get dependsOnStyles()
426 { 450 {
427 return getCachedPropertyValue( 451 return getCachedPropertyValue(
428 this, "_dependsOnStyles", 452 this, "_dependsOnStyles",
429 () => this.selectors.some(selector => selector.dependsOnStyles) 453 () => this.selectors.some(selector => selector.dependsOnStyles)
430 ); 454 );
431 }, 455 },
432 456
433 get dependsOnDOM() 457 get dependsOnDOM()
434 { 458 {
(...skipping 30 matching lines...) Expand all
465 }, 489 },
466 490
467 get dependsOnCharacterData() 491 get dependsOnCharacterData()
468 { 492 {
469 // Observe changes to character data only if there's a contains selector in 493 // Observe changes to character data only if there's a contains selector in
470 // one of the patterns. 494 // one of the patterns.
471 return getCachedPropertyValue( 495 return getCachedPropertyValue(
472 this, "_dependsOnCharacterData", 496 this, "_dependsOnCharacterData",
473 () => this.selectors.some(selector => selector.dependsOnCharacterData) 497 () => this.selectors.some(selector => selector.dependsOnCharacterData)
474 ); 498 );
499 },
500
501 get maybeContainsSiblingCombinators()
502 {
503 return getCachedPropertyValue(
504 this, "_maybeContainsSiblingCombinators",
505 () => this.selectors.some(selector =>
506 selector.maybeContainsSiblingCombinators)
507 );
508 },
509
510 matchesMutationTypes(mutationTypes)
511 {
512 let mutationTypeMatchMap = getCachedPropertyValue(
513 this, "_mutationTypeMatchMap",
514 () => new Map([
515 // All types of DOM-dependent patterns are affected by mutations of
516 // type "childList".
517 ["childList", true],
518 ["attributes", this.maybeDependsOnAttributes],
519 ["characterData", this.dependsOnCharacterData]
520 ])
521 );
522
523 for (let mutationType of mutationTypes)
524 {
525 if (mutationTypeMatchMap.get(mutationType))
526 return true;
527 }
528
529 return false;
475 } 530 }
476 }; 531 };
532
533 function extractMutationTypes(mutations)
534 {
535 let types = new Set();
536
537 for (let mutation of mutations)
538 {
539 types.add(mutation.type);
540
541 // There are only 3 types of mutations: "attributes", "characterData", and
542 // "childList".
543 if (types.size == 3)
544 break;
545 }
546
547 return types;
548 }
477 549
478 function extractMutationTargets(mutations) 550 function extractMutationTargets(mutations)
479 { 551 {
480 if (!mutations) 552 if (!mutations)
481 return null; 553 return null;
482 554
483 let targets = new Set(); 555 let targets = new Set();
484 556
485 for (let mutation of mutations) 557 for (let mutation of mutations)
486 { 558 {
487 if (mutation.type == "childList") 559 if (mutation.type == "childList")
488 { 560 {
489 // When new nodes are added, we're interested in the added nodes rather 561 // When new nodes are added, we're interested in the added nodes rather
490 // than the parent. 562 // than the parent.
491 for (let node of mutation.addedNodes) 563 for (let node of mutation.addedNodes)
492 targets.add(node); 564 targets.add(node);
493
494 // Ideally we would also be interested in removed nodes, but since we
495 // never unhide an element once hidden we can simply ignore any removed
496 // nodes. Note that this will change once we start using CSS selectors
497 // for -abp-has and -abp-contains, i.e. we'll have to remove the
498 // selectors for any removed nodes.
499 } 565 }
500 else 566 else
501 { 567 {
502 targets.add(mutation.target); 568 targets.add(mutation.target);
503 } 569 }
504 } 570 }
505 571
506 return [...targets]; 572 return [...targets];
507 } 573 }
508 574
509 function filterPatterns(patterns, {stylesheets, mutations}) 575 function filterPatterns(patterns, {stylesheets, mutations})
510 { 576 {
511 if (!stylesheets && !mutations) 577 if (!stylesheets && !mutations)
512 return patterns.slice(); 578 return patterns.slice();
513 579
580 let mutationTypes = mutations ? extractMutationTypes(mutations) : null;
581
514 return patterns.filter( 582 return patterns.filter(
515 pattern => (stylesheets && pattern.dependsOnStyles) || 583 pattern => (stylesheets && pattern.dependsOnStyles) ||
516 (mutations && pattern.dependsOnDOM) 584 (mutations && pattern.dependsOnDOM &&
585 pattern.matchesMutationTypes(mutationTypes))
517 ); 586 );
518 } 587 }
519 588
520 function shouldObserveAttributes(patterns) 589 function shouldObserveAttributes(patterns)
521 { 590 {
522 return patterns.some(pattern => pattern.maybeDependsOnAttributes); 591 return patterns.some(pattern => pattern.maybeDependsOnAttributes);
523 } 592 }
524 593
525 function shouldObserveCharacterData(patterns) 594 function shouldObserveCharacterData(patterns)
526 { 595 {
527 return patterns.some(pattern => pattern.dependsOnCharacterData); 596 return patterns.some(pattern => pattern.dependsOnCharacterData);
528 } 597 }
529 598
530 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc) 599 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc)
531 { 600 {
532 this.document = document; 601 this.document = document;
533 this.addSelectorsFunc = addSelectorsFunc; 602 this.addSelectorsFunc = addSelectorsFunc;
534 this.hideElemsFunc = hideElemsFunc; 603 this.hideElemsFunc = hideElemsFunc;
535 this.observer = new MutationObserver(this.observe.bind(this)); 604 this.observer = new MutationObserver(this.observe.bind(this));
605 this.useInlineStyles = true;
536 } 606 }
537 607
538 ElemHideEmulation.prototype = { 608 ElemHideEmulation.prototype = {
539 isSameOrigin(stylesheet) 609 isSameOrigin(stylesheet)
540 { 610 {
541 try 611 try
542 { 612 {
543 return new URL(stylesheet.href).origin == this.document.location.origin; 613 return new URL(stylesheet.href).origin == this.document.location.origin;
544 } 614 }
545 catch (e) 615 catch (e)
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after
622 * @param {MutationRecord[]} [mutations] 692 * @param {MutationRecord[]} [mutations]
623 * The list of DOM mutations that have been applied to the document and 693 * The list of DOM mutations that have been applied to the document and
624 * made reprocessing necessary. This parameter shouldn't be passed in for 694 * made reprocessing necessary. This parameter shouldn't be passed in for
625 * the initial processing, the entire document will be considered 695 * the initial processing, the entire document will be considered
626 * then and all rules, including the ones not dependent on the DOM. 696 * then and all rules, including the ones not dependent on the DOM.
627 * @param {function} [done] 697 * @param {function} [done]
628 * Callback to call when done. 698 * Callback to call when done.
629 */ 699 */
630 _addSelectors(stylesheets, mutations, done) 700 _addSelectors(stylesheets, mutations, done)
631 { 701 {
702 if (testInfo)
703 testInfo.lastProcessedElements.clear();
704
632 let patterns = filterPatterns(this.patterns, {stylesheets, mutations}); 705 let patterns = filterPatterns(this.patterns, {stylesheets, mutations});
633 706
634 let selectors = []; 707 let selectors = [];
635 let selectorFilters = []; 708 let selectorFilters = [];
636 709
637 let elements = []; 710 let elements = [];
638 let elementFilters = []; 711 let elementFilters = [];
639 712
640 let cssStyles = []; 713 let cssStyles = [];
641 714
(...skipping 11 matching lines...) Expand all
653 if (mutations && patterns.some(pattern => pattern.dependsOnStylesAndDOM)) 726 if (mutations && patterns.some(pattern => pattern.dependsOnStylesAndDOM))
654 stylesheets = this.document.styleSheets; 727 stylesheets = this.document.styleSheets;
655 728
656 for (let stylesheet of stylesheets || []) 729 for (let stylesheet of stylesheets || [])
657 { 730 {
658 // Explicitly ignore third-party stylesheets to ensure consistent behavior 731 // Explicitly ignore third-party stylesheets to ensure consistent behavior
659 // between Firefox and Chrome. 732 // between Firefox and Chrome.
660 if (!this.isSameOrigin(stylesheet)) 733 if (!this.isSameOrigin(stylesheet))
661 continue; 734 continue;
662 735
663 let rules = stylesheet.cssRules; 736 let rules;
737 try
738 {
739 rules = stylesheet.cssRules;
740 }
741 catch (e)
742 {
743 // On Firefox, there is a chance that an InvalidAccessError
744 // get thrown when accessing cssRules. Just skip the stylesheet
745 // in that case.
746 // See https://searchfox.org/mozilla-central/rev/f65d7528e34ef1a7665b4a1 a7b7cdb1388fcd3aa/layout/style/StyleSheet.cpp#699
747 continue;
748 }
749
664 if (!rules) 750 if (!rules)
665 continue; 751 continue;
666 752
667 for (let rule of rules) 753 for (let rule of rules)
668 { 754 {
669 if (rule.type != rule.STYLE_RULE) 755 if (rule.type != rule.STYLE_RULE)
670 continue; 756 continue;
671 757
672 cssStyles.push(stringifyStyle(rule)); 758 cssStyles.push(stringifyStyle(rule));
673 } 759 }
(...skipping 16 matching lines...) Expand all
690 this.addSelectorsFunc(selectors, selectorFilters); 776 this.addSelectorsFunc(selectors, selectorFilters);
691 if (elements.length > 0) 777 if (elements.length > 0)
692 this.hideElemsFunc(elements, elementFilters); 778 this.hideElemsFunc(elements, elementFilters);
693 if (typeof done == "function") 779 if (typeof done == "function")
694 done(); 780 done();
695 return; 781 return;
696 } 782 }
697 783
698 pattern = patterns.shift(); 784 pattern = patterns.shift();
699 785
786 let evaluationTargets = targets;
787
788 // If the pattern appears to contain any sibling combinators, we can't
789 // easily optimize based on the mutation targets. Since this is a
790 // special case, skip the optimization. By setting it to null here we
791 // make sure we process the entire DOM.
792 if (pattern.maybeContainsSiblingCombinators)
793 evaluationTargets = null;
794
795 // Ignore mutation targets when using style sheets, because we may have
796 // to update all the CSS selectors.
797 if (!this.useInlineStyles)
798 evaluationTargets = null;
799
700 generator = evaluate(pattern.selectors, 0, "", 800 generator = evaluate(pattern.selectors, 0, "",
701 this.document, cssStyles, targets); 801 this.document, cssStyles, evaluationTargets);
702 } 802 }
703 for (let selector of generator) 803 for (let selector of generator)
704 { 804 {
705 if (selector != null) 805 if (selector != null)
706 { 806 {
707 if (pattern.isSelectorHidingOnlyPattern()) 807 if (!this.useInlineStyles)
708 { 808 {
709 selectors.push(selector); 809 selectors.push(selector);
710 selectorFilters.push(pattern.text); 810 selectorFilters.push(pattern.text);
711 } 811 }
712 else 812 else
713 { 813 {
714 for (let element of this.document.querySelectorAll(selector)) 814 for (let element of this.document.querySelectorAll(selector))
715 { 815 {
716 elements.push(element); 816 elements.push(element);
717 elementFilters.push(pattern.text); 817 elementFilters.push(pattern.text);
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
769 this.queueFiltering(params.stylesheets, params.mutations); 869 this.queueFiltering(params.stylesheets, params.mutations);
770 } 870 }
771 }; 871 };
772 872
773 if (this._scheduledProcessing) 873 if (this._scheduledProcessing)
774 { 874 {
775 if (!stylesheets && !mutations) 875 if (!stylesheets && !mutations)
776 { 876 {
777 this._scheduledProcessing = {}; 877 this._scheduledProcessing = {};
778 } 878 }
779 else 879 else if (this._scheduledProcessing.stylesheets ||
880 this._scheduledProcessing.mutations)
780 { 881 {
781 if (stylesheets) 882 if (stylesheets)
782 { 883 {
783 if (!this._scheduledProcessing.stylesheets) 884 if (!this._scheduledProcessing.stylesheets)
784 this._scheduledProcessing.stylesheets = []; 885 this._scheduledProcessing.stylesheets = [];
785 this._scheduledProcessing.stylesheets.push(...stylesheets); 886 this._scheduledProcessing.stylesheets.push(...stylesheets);
786 } 887 }
787 if (mutations) 888 if (mutations)
788 { 889 {
789 if (!this._scheduledProcessing.mutations) 890 if (!this._scheduledProcessing.mutations)
(...skipping 16 matching lines...) Expand all
806 this._scheduledProcessing = null; 907 this._scheduledProcessing = null;
807 this._addSelectors(params.stylesheets, params.mutations, completion); 908 this._addSelectors(params.stylesheets, params.mutations, completion);
808 }, 909 },
809 MIN_INVOCATION_INTERVAL - (performance.now() - this._lastInvocation)); 910 MIN_INVOCATION_INTERVAL - (performance.now() - this._lastInvocation));
810 } 911 }
811 else if (this.document.readyState == "loading") 912 else if (this.document.readyState == "loading")
812 { 913 {
813 this._scheduledProcessing = {stylesheets, mutations}; 914 this._scheduledProcessing = {stylesheets, mutations};
814 let handler = () => 915 let handler = () =>
815 { 916 {
816 document.removeEventListener("DOMContentLoaded", handler); 917 this.document.removeEventListener("DOMContentLoaded", handler);
817 let params = Object.assign({}, this._scheduledProcessing); 918 let params = Object.assign({}, this._scheduledProcessing);
818 this._filteringInProgress = true; 919 this._filteringInProgress = true;
819 this._scheduledProcessing = null; 920 this._scheduledProcessing = null;
820 this._addSelectors(params.stylesheets, params.mutations, completion); 921 this._addSelectors(params.stylesheets, params.mutations, completion);
821 }; 922 };
822 document.addEventListener("DOMContentLoaded", handler); 923 this.document.addEventListener("DOMContentLoaded", handler);
823 } 924 }
824 else 925 else
825 { 926 {
826 this._filteringInProgress = true; 927 this._filteringInProgress = true;
827 this._addSelectors(stylesheets, mutations, completion); 928 this._addSelectors(stylesheets, mutations, completion);
828 } 929 }
829 }, 930 },
830 931
831 onLoad(event) 932 onLoad(event)
832 { 933 {
833 let stylesheet = event.target.sheet; 934 let stylesheet = event.target.sheet;
834 if (stylesheet) 935 if (stylesheet)
835 this.queueFiltering([stylesheet]); 936 this.queueFiltering([stylesheet]);
836 }, 937 },
837 938
838 observe(mutations) 939 observe(mutations)
839 { 940 {
941 if (testInfo)
942 {
943 // In test mode, filter out any mutations likely done by us
944 // (i.e. style="display: none !important"). This makes it easier to
945 // observe how the code responds to DOM mutations.
946 mutations = mutations.filter(
947 ({type, attributeName, target: {style: newValue}, oldValue}) =>
948 !(type == "attributes" && attributeName == "style" &&
949 newValue.display == "none" && oldValue.display != "none")
950 );
951
952 if (mutations.length == 0)
953 return;
954 }
hub 2018/05/24 19:19:09 ... and this :-/ There should be a better way to
Manish Jethani 2018/05/25 07:21:25 OK, do you have any suggestions? This is only done
955
840 this.queueFiltering(null, mutations); 956 this.queueFiltering(null, mutations);
841 }, 957 },
842 958
843 apply(patterns) 959 apply(patterns)
844 { 960 {
845 this.patterns = []; 961 this.patterns = [];
846 for (let pattern of patterns) 962 for (let pattern of patterns)
847 { 963 {
848 let selectors = this.parseSelector(pattern.selector); 964 let selectors = this.parseSelector(pattern.selector);
849 if (selectors != null && selectors.length > 0) 965 if (selectors != null && selectors.length > 0)
(...skipping 11 matching lines...) Expand all
861 characterData: shouldObserveCharacterData(this.patterns), 977 characterData: shouldObserveCharacterData(this.patterns),
862 subtree: true 978 subtree: true
863 } 979 }
864 ); 980 );
865 this.document.addEventListener("load", this.onLoad.bind(this), true); 981 this.document.addEventListener("load", this.onLoad.bind(this), true);
866 } 982 }
867 } 983 }
868 }; 984 };
869 985
870 exports.ElemHideEmulation = ElemHideEmulation; 986 exports.ElemHideEmulation = ElemHideEmulation;
LEFTRIGHT

Powered by Google App Engine
This is Rietveld