| Left: | ||
| Right: |
| 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 |
| 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 function getCachedPropertyValue(object, name, defaultValueFunc = () => {}) | |
| 28 { | |
| 29 let value = object[name]; | |
| 30 if (typeof value == "undefined") | |
| 31 Object.defineProperty(object, name, {value: value = defaultValueFunc()}); | |
| 32 return value; | |
| 33 } | |
| 25 | 34 |
| 26 /** Return position of node from parent. | 35 /** Return position of node from parent. |
| 27 * @param {Node} node the node to find the position of. | 36 * @param {Node} node the node to find the position of. |
| 28 * @return {number} One-based index like for :nth-child(), or 0 on error. | 37 * @return {number} One-based index like for :nth-child(), or 0 on error. |
| 29 */ | 38 */ |
| 30 function positionInParent(node) | 39 function positionInParent(node) |
| 31 { | 40 { |
| 32 let {children} = node.parentNode; | 41 return indexOf(node.parentNode.children, node) + 1; |
| 33 for (let i = 0; i < children.length; i++) | 42 } |
| 34 if (children[i] == node) | 43 |
| 35 return i + 1; | 44 function makeSelector(node, selector = "") |
| 36 return 0; | |
| 37 } | |
| 38 | |
| 39 function makeSelector(node, selector) | |
| 40 { | 45 { |
| 41 if (node == null) | 46 if (node == null) |
| 42 return null; | 47 return null; |
| 43 if (!node.parentElement) | 48 if (!node.parentElement) |
| 44 { | 49 { |
| 45 let newSelector = ":root"; | 50 let newSelector = ":root"; |
| 46 if (selector) | 51 if (selector) |
| 47 newSelector += " > " + selector; | 52 newSelector += " > " + selector; |
| 48 return newSelector; | 53 return newSelector; |
| 49 } | 54 } |
| (...skipping 187 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 237 | 242 |
| 238 const incompletePrefixRegexp = /[\s>+~]$/; | 243 const incompletePrefixRegexp = /[\s>+~]$/; |
| 239 | 244 |
| 240 function HasSelector(selectors) | 245 function HasSelector(selectors) |
| 241 { | 246 { |
| 242 this._innerSelectors = selectors; | 247 this._innerSelectors = selectors; |
| 243 } | 248 } |
| 244 | 249 |
| 245 HasSelector.prototype = { | 250 HasSelector.prototype = { |
| 246 requiresHiding: true, | 251 requiresHiding: true, |
| 252 dependsOnDOM: true, | |
| 247 | 253 |
| 248 get dependsOnStyles() | 254 get dependsOnStyles() |
| 249 { | 255 { |
| 250 return this._innerSelectors.some(selector => selector.dependsOnStyles); | 256 return this._innerSelectors.some(selector => selector.dependsOnStyles); |
| 251 }, | 257 }, |
| 252 | 258 |
| 253 get dependsOnCharacterData() | 259 get dependsOnCharacterData() |
| 254 { | 260 { |
| 255 return this._innerSelectors.some( | 261 return this._innerSelectors.some( |
| 256 selector => selector.dependsOnCharacterData | 262 selector => selector.dependsOnCharacterData |
| 257 ); | 263 ); |
| 258 }, | 264 }, |
| 259 | 265 |
| 260 get maybeDependsOnAttributes() | 266 get maybeDependsOnAttributes() |
| 261 { | 267 { |
| 262 return this._innerSelectors.some( | 268 return this._innerSelectors.some( |
| 263 selector => selector.maybeDependsOnAttributes | 269 selector => selector.maybeDependsOnAttributes |
| 264 ); | 270 ); |
| 265 }, | 271 }, |
| 266 | 272 |
| 267 *getSelectors(prefix, subtree, styles) | 273 *getSelectors(prefix, subtree, styles) |
| 268 { | 274 { |
| 269 for (let element of this.getElements(prefix, subtree, styles)) | 275 for (let element of this.getElements(prefix, subtree, styles)) |
| 270 yield [makeSelector(element, ""), element]; | 276 yield [makeSelector(element), element]; |
| 271 }, | 277 }, |
| 272 | 278 |
| 273 /** | 279 /** |
| 274 * Generator function returning selected elements. | 280 * Generator function returning selected elements. |
| 275 * @param {string} prefix the prefix for the selector. | 281 * @param {string} prefix the prefix for the selector. |
| 276 * @param {Node} subtree the subtree we work on. | 282 * @param {Node} subtree the subtree we work on. |
| 277 * @param {StringifiedStyle[]} styles the stringified style objects. | 283 * @param {StringifiedStyle[]} styles the stringified style objects. |
| 278 */ | 284 */ |
| 279 *getElements(prefix, subtree, styles) | 285 *getElements(prefix, subtree, styles) |
| 280 { | 286 { |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 299 } | 305 } |
| 300 }; | 306 }; |
| 301 | 307 |
| 302 function ContainsSelector(textContent) | 308 function ContainsSelector(textContent) |
| 303 { | 309 { |
| 304 this._regexp = makeRegExpParameter(textContent); | 310 this._regexp = makeRegExpParameter(textContent); |
| 305 } | 311 } |
| 306 | 312 |
| 307 ContainsSelector.prototype = { | 313 ContainsSelector.prototype = { |
| 308 requiresHiding: true, | 314 requiresHiding: true, |
| 315 dependsOnDOM: true, | |
| 309 dependsOnCharacterData: true, | 316 dependsOnCharacterData: true, |
| 310 | 317 |
| 311 *getSelectors(prefix, subtree, styles) | 318 *getSelectors(prefix, subtree, styles) |
| 312 { | 319 { |
| 313 for (let element of this.getElements(prefix, subtree, styles)) | 320 for (let element of this.getElements(prefix, subtree, styles)) |
| 314 yield [makeSelector(element, ""), subtree]; | 321 yield [makeSelector(element), subtree]; |
| 315 }, | 322 }, |
| 316 | 323 |
| 317 *getElements(prefix, subtree, styles) | 324 *getElements(prefix, subtree, styles) |
| 318 { | 325 { |
| 319 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? | 326 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? |
| 320 prefix + "*" : prefix; | 327 prefix + "*" : prefix; |
| 321 | 328 |
| 322 let elements = scopedQuerySelectorAll(subtree, actualPrefix); | 329 let elements = scopedQuerySelectorAll(subtree, actualPrefix); |
| 323 if (elements) | 330 if (elements) |
| 324 { | 331 { |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 370 } | 377 } |
| 371 }, | 378 }, |
| 372 | 379 |
| 373 *getSelectors(prefix, subtree, styles) | 380 *getSelectors(prefix, subtree, styles) |
| 374 { | 381 { |
| 375 for (let selector of this.findPropsSelectors(styles, prefix, this._regexp)) | 382 for (let selector of this.findPropsSelectors(styles, prefix, this._regexp)) |
| 376 yield [selector, subtree]; | 383 yield [selector, subtree]; |
| 377 } | 384 } |
| 378 }; | 385 }; |
| 379 | 386 |
| 380 function isSelectorHidingOnlyPattern(pattern) | 387 function Pattern(selectors, text) |
| 381 { | 388 { |
| 382 return pattern.selectors.some(s => s.preferHideWithSelector) && | 389 this.selectors = selectors; |
| 383 !pattern.selectors.some(s => s.requiresHiding); | 390 this.text = text; |
| 391 } | |
| 392 | |
| 393 Pattern.prototype = { | |
| 394 isSelectorHidingOnlyPattern() | |
| 395 { | |
| 396 return getCachedPropertyValue( | |
| 397 this, "_selectorHidingOnlyPattern", | |
| 398 () => this.selectors.some(selector => selector.preferHideWithSelector) && | |
| 399 !this.selectors.some(selector => selector.requiresHiding) | |
| 400 ); | |
| 401 }, | |
| 402 | |
| 403 get dependsOnStyles() | |
| 404 { | |
| 405 return getCachedPropertyValue( | |
| 406 this, "_dependsOnStyles", | |
| 407 () => this.selectors.some(selector => selector.dependsOnStyles) | |
| 408 ); | |
| 409 }, | |
| 410 | |
| 411 get dependsOnDOM() | |
| 412 { | |
| 413 return getCachedPropertyValue( | |
| 414 this, "_dependsOnDOM", | |
| 415 () => this.selectors.some(selector => selector.dependsOnDOM) | |
| 416 ); | |
| 417 }, | |
| 418 | |
| 419 get dependsOnStylesAndDOM() | |
| 420 { | |
| 421 return getCachedPropertyValue( | |
| 422 this, "_dependsOnStylesAndDOM", | |
| 423 () => this.selectors.some(selector => selector.dependsOnStyles && | |
| 424 selector.dependsOnDOM) | |
| 425 ); | |
| 426 }, | |
| 427 | |
| 428 get maybeDependsOnAttributes() | |
| 429 { | |
| 430 // Observe changes to attributes if either there's a plain selector that | |
| 431 // looks like an ID selector, class selector, or attribute selector in one | |
| 432 // of the patterns (e.g. "a[href='https://example.com/']") | |
| 433 // or there's a properties selector nested inside a has selector | |
| 434 // (e.g. "div:-abp-has(:-abp-properties(color: blue))") | |
| 435 return getCachedPropertyValue( | |
| 436 this, "_maybeDependsOnAttributes", | |
| 437 () => this.selectors.some( | |
| 438 selector => selector.maybeDependsOnAttributes || | |
| 439 (selector instanceof HasSelector && | |
| 440 selector.dependsOnStyles) | |
| 441 ) | |
| 442 ); | |
| 443 }, | |
| 444 | |
| 445 get dependsOnCharacterData() | |
| 446 { | |
| 447 // Observe changes to character data only if there's a contains selector in | |
| 448 // one of the patterns. | |
| 449 return getCachedPropertyValue( | |
| 450 this, "_dependsOnCharacterData", | |
| 451 () => this.selectors.some(selector => selector.dependsOnCharacterData) | |
| 452 ); | |
| 453 } | |
| 454 }; | |
| 455 | |
| 456 function filterPatterns(patterns, {stylesheets, mutations}) | |
| 457 { | |
| 458 if (!stylesheets && !mutations) | |
| 459 return patterns.slice(); | |
| 460 | |
| 461 return patterns.filter( | |
| 462 pattern => (stylesheets && pattern.dependsOnStyles) || | |
| 463 (mutations && pattern.dependsOnDOM) | |
| 464 ); | |
| 384 } | 465 } |
| 385 | 466 |
| 386 function shouldObserveAttributes(patterns) | 467 function shouldObserveAttributes(patterns) |
| 387 { | 468 { |
| 388 // Observe changes to attributes if either there's a plain selector that | 469 return patterns.some(pattern => pattern.maybeDependsOnAttributes); |
| 389 // looks like an ID selector, class selector, or attribute selector in one of | |
| 390 // the patterns (e.g. "a[href='https://example.com/']") | |
| 391 // or there's a properties selector nested inside a has selector | |
| 392 // (e.g. "div:-abp-has(:-abp-properties(color: blue))") | |
| 393 return patterns.some( | |
| 394 pattern => pattern.selectors.some( | |
| 395 selector => selector.maybeDependsOnAttributes || | |
| 396 (selector instanceof HasSelector && | |
| 397 selector.dependsOnStyles) | |
| 398 ) | |
| 399 ); | |
| 400 } | 470 } |
| 401 | 471 |
| 402 function shouldObserveCharacterData(patterns) | 472 function shouldObserveCharacterData(patterns) |
| 403 { | 473 { |
| 404 // Observe changes to character data only if there's a contains selector in | 474 return patterns.some(pattern => pattern.dependsOnCharacterData); |
| 405 // one of the patterns. | |
| 406 return patterns.some( | |
| 407 pattern => pattern.selectors.some( | |
| 408 selector => selector.dependsOnCharacterData | |
| 409 ) | |
| 410 ); | |
| 411 } | 475 } |
| 412 | 476 |
| 413 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc) | 477 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc) |
| 414 { | 478 { |
| 415 this.document = document; | 479 this.document = document; |
| 416 this.addSelectorsFunc = addSelectorsFunc; | 480 this.addSelectorsFunc = addSelectorsFunc; |
| 417 this.hideElemsFunc = hideElemsFunc; | 481 this.hideElemsFunc = hideElemsFunc; |
| 418 this.observer = new MutationObserver(this.observe.bind(this)); | 482 this.observer = new MutationObserver(this.observe.bind(this)); |
| 483 this.useInlineStyles = true; | |
| 419 } | 484 } |
| 420 | 485 |
| 421 ElemHideEmulation.prototype = { | 486 ElemHideEmulation.prototype = { |
| 422 isSameOrigin(stylesheet) | 487 isSameOrigin(stylesheet) |
| 423 { | 488 { |
| 424 try | 489 try |
| 425 { | 490 { |
| 426 return new URL(stylesheet.href).origin == this.document.location.origin; | 491 return new URL(stylesheet.href).origin == this.document.location.origin; |
| 427 } | 492 } |
| 428 catch (e) | 493 catch (e) |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 495 return selectors; | 560 return selectors; |
| 496 }, | 561 }, |
| 497 | 562 |
| 498 /** | 563 /** |
| 499 * Processes the current document and applies all rules to it. | 564 * Processes the current document and applies all rules to it. |
| 500 * @param {CSSStyleSheet[]} [stylesheets] | 565 * @param {CSSStyleSheet[]} [stylesheets] |
| 501 * The list of new stylesheets that have been added to the document and | 566 * The list of new stylesheets that have been added to the document and |
| 502 * made reprocessing necessary. This parameter shouldn't be passed in for | 567 * made reprocessing necessary. This parameter shouldn't be passed in for |
| 503 * the initial processing, all of document's stylesheets will be considered | 568 * the initial processing, all of document's stylesheets will be considered |
| 504 * then and all rules, including the ones not dependent on styles. | 569 * then and all rules, including the ones not dependent on styles. |
| 570 * @param {MutationRecord[]} [mutations] | |
| 571 * The list of DOM mutations that have been applied to the document and | |
| 572 * made reprocessing necessary. This parameter shouldn't be passed in for | |
| 573 * the initial processing, the entire document will be considered | |
| 574 * then and all rules, including the ones not dependent on the DOM. | |
| 505 * @param {function} [done] | 575 * @param {function} [done] |
| 506 * Callback to call when done. | 576 * Callback to call when done. |
| 507 */ | 577 */ |
| 508 _addSelectors(stylesheets, done) | 578 _addSelectors(stylesheets, mutations, done) |
| 509 { | 579 { |
| 580 let patterns = filterPatterns(this.patterns, {stylesheets, mutations}); | |
| 581 | |
| 510 let selectors = []; | 582 let selectors = []; |
| 511 let selectorFilters = []; | 583 let selectorFilters = []; |
| 512 | 584 |
| 513 let elements = []; | 585 let elements = []; |
| 514 let elementFilters = []; | 586 let elementFilters = []; |
| 515 | 587 |
| 516 let cssStyles = []; | 588 let cssStyles = []; |
| 517 | 589 |
| 518 let stylesheetOnlyChange = !!stylesheets; | 590 // If neither any style sheets nor any DOM mutations have been specified, |
| 519 if (!stylesheets) | 591 // do full processing. |
| 592 if (!stylesheets && !mutations) | |
| 520 stylesheets = this.document.styleSheets; | 593 stylesheets = this.document.styleSheets; |
| 521 | 594 |
| 522 for (let stylesheet of stylesheets) | 595 // If there are any DOM mutations and any of the patterns depends on both |
| 596 // style sheets and the DOM (e.g. -abp-has(-abp-properties)), find all the | |
| 597 // rules in every style sheet in the document, because we need to run | |
| 598 // querySelectorAll afterwards. On the other hand, if we only have patterns | |
| 599 // that depend on either styles or DOM both not both | |
| 600 // (e.g. -abp-properties or -abp-contains), we can skip this part. | |
| 601 if (mutations && patterns.some(pattern => pattern.dependsOnStylesAndDOM)) | |
| 602 stylesheets = this.document.styleSheets; | |
| 603 | |
| 604 for (let stylesheet of stylesheets || []) | |
| 523 { | 605 { |
| 524 // Explicitly ignore third-party stylesheets to ensure consistent behavior | 606 // Explicitly ignore third-party stylesheets to ensure consistent behavior |
| 525 // between Firefox and Chrome. | 607 // between Firefox and Chrome. |
| 526 if (!this.isSameOrigin(stylesheet)) | 608 if (!this.isSameOrigin(stylesheet)) |
| 527 continue; | 609 continue; |
| 528 | 610 |
| 611 let rules; | |
| 529 try | 612 try |
| 530 { | 613 { |
| 531 let rules = stylesheet.cssRules; | 614 rules = stylesheet.cssRules; |
|
kzar
2018/03/19 15:52:07
If this is the only line that could cause the exce
hub
2018/03/20 14:53:53
Done.
| |
| 532 if (!rules) | |
| 533 continue; | |
| 534 | |
| 535 for (let rule of rules) | |
| 536 { | |
| 537 if (rule.type != rule.STYLE_RULE) | |
| 538 continue; | |
| 539 | |
| 540 cssStyles.push(stringifyStyle(rule)); | |
| 541 } | |
| 542 } | 615 } |
| 543 catch (e) | 616 catch (e) |
| 544 { | 617 { |
| 545 // On Firefox, there is a chance that an InvalidAccessError | 618 // On Firefox, there is a chance that an InvalidAccessError |
|
kzar
2018/03/19 15:52:07
How come this happens? Is there a Firefox bug / di
hub
2018/03/20 16:16:02
It's not a bug.
https://searchfox.org/mozilla-cen
kzar
2018/04/18 16:12:30
Acknowledged.
In fact that link you gave is prett
hub
2018/04/18 18:48:23
Done.
| |
| 546 // get thrown when accessing cssRules. Just skip the stylesheet | 619 // get thrown when accessing cssRules. Just skip the stylesheet |
| 547 // in that case. | 620 // in that case. |
| 548 // See https://issues.adblockplus.org/ticket/6382 | 621 // See https://searchfox.org/mozilla-central/rev/f65d7528e34ef1a7665b4a1 a7b7cdb1388fcd3aa/layout/style/StyleSheet.cpp#699 |
| 549 continue; | 622 continue; |
| 550 } | 623 } |
| 551 } | 624 |
| 552 | 625 if (!rules) |
| 553 let patterns = this.patterns.slice(); | 626 continue; |
| 627 | |
| 628 for (let rule of rules) | |
| 629 { | |
| 630 if (rule.type != rule.STYLE_RULE) | |
| 631 continue; | |
| 632 | |
| 633 cssStyles.push(stringifyStyle(rule)); | |
| 634 } | |
| 635 } | |
| 636 | |
| 554 let pattern = null; | 637 let pattern = null; |
| 555 let generator = null; | 638 let generator = null; |
| 556 | 639 |
| 557 let processPatterns = () => | 640 let processPatterns = () => |
| 558 { | 641 { |
| 559 let cycleStart = performance.now(); | 642 let cycleStart = performance.now(); |
| 560 | 643 |
| 561 if (!pattern) | 644 if (!pattern) |
| 562 { | 645 { |
| 563 if (!patterns.length) | 646 if (!patterns.length) |
| 564 { | 647 { |
| 565 this.addSelectorsFunc(selectors, selectorFilters); | 648 if (selectors.length > 0) |
| 566 this.hideElemsFunc(elements, elementFilters); | 649 this.addSelectorsFunc(selectors, selectorFilters); |
| 650 if (elements.length > 0) | |
| 651 this.hideElemsFunc(elements, elementFilters); | |
| 567 if (typeof done == "function") | 652 if (typeof done == "function") |
| 568 done(); | 653 done(); |
| 569 return; | 654 return; |
| 570 } | 655 } |
| 571 | 656 |
| 572 pattern = patterns.shift(); | 657 pattern = patterns.shift(); |
| 573 | 658 |
| 574 if (stylesheetOnlyChange && | |
| 575 !pattern.selectors.some(selector => selector.dependsOnStyles)) | |
| 576 { | |
| 577 pattern = null; | |
| 578 return processPatterns(); | |
| 579 } | |
| 580 generator = evaluate(pattern.selectors, 0, "", | 659 generator = evaluate(pattern.selectors, 0, "", |
| 581 this.document, cssStyles); | 660 this.document, cssStyles); |
| 582 } | 661 } |
| 583 for (let selector of generator) | 662 for (let selector of generator) |
| 584 { | 663 { |
| 585 if (selector != null) | 664 if (selector != null) |
| 586 { | 665 { |
| 587 if (isSelectorHidingOnlyPattern(pattern)) | 666 if (!this.useInlineStyles || |
| 667 pattern.isSelectorHidingOnlyPattern()) | |
| 588 { | 668 { |
| 589 selectors.push(selector); | 669 selectors.push(selector); |
| 590 selectorFilters.push(pattern.text); | 670 selectorFilters.push(pattern.text); |
| 591 } | 671 } |
| 592 else | 672 else |
| 593 { | 673 { |
| 594 for (let element of this.document.querySelectorAll(selector)) | 674 for (let element of this.document.querySelectorAll(selector)) |
| 595 { | 675 { |
| 596 elements.push(element); | 676 elements.push(element); |
| 597 elementFilters.push(pattern.text); | 677 elementFilters.push(pattern.text); |
| (...skipping 26 matching lines...) Expand all Loading... | |
| 624 }, | 704 }, |
| 625 | 705 |
| 626 _filteringInProgress: false, | 706 _filteringInProgress: false, |
| 627 _lastInvocation: -MIN_INVOCATION_INTERVAL, | 707 _lastInvocation: -MIN_INVOCATION_INTERVAL, |
| 628 _scheduledProcessing: null, | 708 _scheduledProcessing: null, |
| 629 | 709 |
| 630 /** | 710 /** |
| 631 * Re-run filtering either immediately or queued. | 711 * Re-run filtering either immediately or queued. |
| 632 * @param {CSSStyleSheet[]} [stylesheets] | 712 * @param {CSSStyleSheet[]} [stylesheets] |
| 633 * new stylesheets to be processed. This parameter should be omitted | 713 * new stylesheets to be processed. This parameter should be omitted |
| 634 * for DOM modification (full reprocessing required). | 714 * for full reprocessing. |
| 715 * @param {MutationRecord[]} [mutations] | |
| 716 * new DOM mutations to be processed. This parameter should be omitted | |
| 717 * for full reprocessing. | |
| 635 */ | 718 */ |
| 636 queueFiltering(stylesheets) | 719 queueFiltering(stylesheets, mutations) |
| 637 { | 720 { |
| 638 let completion = () => | 721 let completion = () => |
| 639 { | 722 { |
| 640 this._lastInvocation = performance.now(); | 723 this._lastInvocation = performance.now(); |
| 641 this._filteringInProgress = false; | 724 this._filteringInProgress = false; |
| 642 if (this._scheduledProcessing) | 725 if (this._scheduledProcessing) |
| 643 { | 726 { |
| 644 let newStylesheets = this._scheduledProcessing.stylesheets; | 727 let params = Object.assign({}, this._scheduledProcessing); |
| 645 this._scheduledProcessing = null; | 728 this._scheduledProcessing = null; |
| 646 this.queueFiltering(newStylesheets); | 729 this.queueFiltering(params.stylesheets, params.mutations); |
| 647 } | 730 } |
| 648 }; | 731 }; |
| 649 | 732 |
| 650 if (this._scheduledProcessing) | 733 if (this._scheduledProcessing) |
| 651 { | 734 { |
| 652 if (!stylesheets) | 735 if (!stylesheets && !mutations) |
| 653 this._scheduledProcessing.stylesheets = null; | 736 { |
| 654 else if (this._scheduledProcessing.stylesheets) | 737 this._scheduledProcessing = {}; |
| 655 this._scheduledProcessing.stylesheets.push(...stylesheets); | 738 } |
| 739 else if (this._scheduledProcessing.stylesheets || | |
| 740 this._scheduledProcessing.mutations) | |
| 741 { | |
| 742 if (stylesheets) | |
| 743 { | |
| 744 if (!this._scheduledProcessing.stylesheets) | |
| 745 this._scheduledProcessing.stylesheets = []; | |
| 746 this._scheduledProcessing.stylesheets.push(...stylesheets); | |
| 747 } | |
| 748 if (mutations) | |
| 749 { | |
| 750 if (!this._scheduledProcessing.mutations) | |
| 751 this._scheduledProcessing.mutations = []; | |
| 752 this._scheduledProcessing.mutations.push(...mutations); | |
| 753 } | |
| 754 } | |
| 656 } | 755 } |
| 657 else if (this._filteringInProgress) | 756 else if (this._filteringInProgress) |
| 658 { | 757 { |
| 659 this._scheduledProcessing = {stylesheets}; | 758 this._scheduledProcessing = {stylesheets, mutations}; |
| 660 } | 759 } |
| 661 else if (performance.now() - this._lastInvocation < MIN_INVOCATION_INTERVAL) | 760 else if (performance.now() - this._lastInvocation < MIN_INVOCATION_INTERVAL) |
| 662 { | 761 { |
| 663 this._scheduledProcessing = {stylesheets}; | 762 this._scheduledProcessing = {stylesheets, mutations}; |
| 664 setTimeout(() => | 763 setTimeout(() => |
| 665 { | 764 { |
| 666 let newStylesheets = this._scheduledProcessing.stylesheets; | 765 let params = Object.assign({}, this._scheduledProcessing); |
| 667 this._filteringInProgress = true; | 766 this._filteringInProgress = true; |
| 668 this._scheduledProcessing = null; | 767 this._scheduledProcessing = null; |
| 669 this._addSelectors(newStylesheets, completion); | 768 this._addSelectors(params.stylesheets, params.mutations, completion); |
| 670 }, | 769 }, |
| 671 MIN_INVOCATION_INTERVAL - (performance.now() - this._lastInvocation)); | 770 MIN_INVOCATION_INTERVAL - (performance.now() - this._lastInvocation)); |
| 672 } | 771 } |
| 673 else if (this.document.readyState == "loading") | 772 else if (this.document.readyState == "loading") |
| 674 { | 773 { |
| 675 this._scheduledProcessing = {stylesheets}; | 774 this._scheduledProcessing = {stylesheets, mutations}; |
| 676 let handler = () => | 775 let handler = () => |
| 677 { | 776 { |
| 678 document.removeEventListener("DOMContentLoaded", handler); | 777 this.document.removeEventListener("DOMContentLoaded", handler); |
| 679 let newStylesheets = this._scheduledProcessing.stylesheets; | 778 let params = Object.assign({}, this._scheduledProcessing); |
| 680 this._filteringInProgress = true; | 779 this._filteringInProgress = true; |
| 681 this._scheduledProcessing = null; | 780 this._scheduledProcessing = null; |
| 682 this._addSelectors(newStylesheets, completion); | 781 this._addSelectors(params.stylesheets, params.mutations, completion); |
| 683 }; | 782 }; |
| 684 document.addEventListener("DOMContentLoaded", handler); | 783 this.document.addEventListener("DOMContentLoaded", handler); |
| 685 } | 784 } |
| 686 else | 785 else |
| 687 { | 786 { |
| 688 this._filteringInProgress = true; | 787 this._filteringInProgress = true; |
| 689 this._addSelectors(stylesheets, completion); | 788 this._addSelectors(stylesheets, mutations, completion); |
| 690 } | 789 } |
| 691 }, | 790 }, |
| 692 | 791 |
| 693 onLoad(event) | 792 onLoad(event) |
| 694 { | 793 { |
| 695 let stylesheet = event.target.sheet; | 794 let stylesheet = event.target.sheet; |
| 696 if (stylesheet) | 795 if (stylesheet) |
| 697 this.queueFiltering([stylesheet]); | 796 this.queueFiltering([stylesheet]); |
| 698 }, | 797 }, |
| 699 | 798 |
| 700 observe(mutations) | 799 observe(mutations) |
| 701 { | 800 { |
| 702 this.queueFiltering(); | 801 this.queueFiltering(null, mutations); |
| 703 }, | 802 }, |
| 704 | 803 |
| 705 apply(patterns) | 804 apply(patterns) |
| 706 { | 805 { |
| 707 this.patterns = []; | 806 this.patterns = []; |
| 708 for (let pattern of patterns) | 807 for (let pattern of patterns) |
| 709 { | 808 { |
| 710 let selectors = this.parseSelector(pattern.selector); | 809 let selectors = this.parseSelector(pattern.selector); |
| 711 if (selectors != null && selectors.length > 0) | 810 if (selectors != null && selectors.length > 0) |
| 712 this.patterns.push({selectors, text: pattern.text}); | 811 this.patterns.push(new Pattern(selectors, pattern.text)); |
| 713 } | 812 } |
| 714 | 813 |
| 715 if (this.patterns.length > 0) | 814 if (this.patterns.length > 0) |
| 716 { | 815 { |
| 717 this.queueFiltering(); | 816 this.queueFiltering(); |
| 718 this.observer.observe( | 817 this.observer.observe( |
| 719 this.document, | 818 this.document, |
| 720 { | 819 { |
| 721 childList: true, | 820 childList: true, |
| 722 attributes: shouldObserveAttributes(this.patterns), | 821 attributes: shouldObserveAttributes(this.patterns), |
| 723 characterData: shouldObserveCharacterData(this.patterns), | 822 characterData: shouldObserveCharacterData(this.patterns), |
| 724 subtree: true | 823 subtree: true |
| 725 } | 824 } |
| 726 ); | 825 ); |
| 727 this.document.addEventListener("load", this.onLoad.bind(this), true); | 826 this.document.addEventListener("load", this.onLoad.bind(this), true); |
| 728 } | 827 } |
| 729 } | 828 } |
| 730 }; | 829 }; |
| 731 | 830 |
| 732 exports.ElemHideEmulation = ElemHideEmulation; | 831 exports.ElemHideEmulation = ElemHideEmulation; |
| LEFT | RIGHT |