| 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 284 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 295 yield [selector, subtree]; | 295 yield [selector, subtree]; |
| 296 } | 296 } |
| 297 }; | 297 }; |
| 298 | 298 |
| 299 function isSelectorHidingOnlyPattern(pattern) | 299 function isSelectorHidingOnlyPattern(pattern) |
| 300 { | 300 { |
| 301 return pattern.selectors.some(s => s.preferHideWithSelector) && | 301 return pattern.selectors.some(s => s.preferHideWithSelector) && |
| 302 !pattern.selectors.some(s => s.requiresHiding); | 302 !pattern.selectors.some(s => s.requiresHiding); |
| 303 } | 303 } |
| 304 | 304 |
| 305 function ElemHideEmulation(window, getFiltersFunc, addSelectorsFunc, | 305 function ElemHideEmulation(addSelectorsFunc, hideElemsFunc) |
| 306 hideElemsFunc) | |
| 307 { | 306 { |
| 308 this.window = window; | 307 this.document = document; |
| 309 this.getFiltersFunc = getFiltersFunc; | |
| 310 this.addSelectorsFunc = addSelectorsFunc; | 308 this.addSelectorsFunc = addSelectorsFunc; |
| 311 this.hideElemsFunc = hideElemsFunc; | 309 this.hideElemsFunc = hideElemsFunc; |
| 312 this.observer = new window.MutationObserver(this.observe.bind(this)); | 310 this.observer = new MutationObserver(this.observe.bind(this)); |
| 313 } | 311 } |
| 314 | 312 |
| 315 ElemHideEmulation.prototype = { | 313 ElemHideEmulation.prototype = { |
| 316 isSameOrigin(stylesheet) | 314 isSameOrigin(stylesheet) |
| 317 { | 315 { |
| 318 try | 316 try |
| 319 { | 317 { |
| 320 return new URL(stylesheet.href).origin == this.window.location.origin; | 318 return new URL(stylesheet.href).origin == this.document.location.origin; |
| 321 } | 319 } |
| 322 catch (e) | 320 catch (e) |
| 323 { | 321 { |
| 324 // Invalid URL, assume that it is first-party. | 322 // Invalid URL, assume that it is first-party. |
| 325 return true; | 323 return true; |
| 326 } | 324 } |
| 327 }, | 325 }, |
| 328 | 326 |
| 329 /** Parse the selector | 327 /** Parse the selector |
| 330 * @param {string} selector the selector to parse | 328 * @param {string} selector the selector to parse |
| (...skipping 10 matching lines...) Expand all Loading... |
| 341 return [new PlainSelector(selector)]; | 339 return [new PlainSelector(selector)]; |
| 342 | 340 |
| 343 let selectors = []; | 341 let selectors = []; |
| 344 if (match.index > 0) | 342 if (match.index > 0) |
| 345 selectors.push(new PlainSelector(selector.substr(0, match.index))); | 343 selectors.push(new PlainSelector(selector.substr(0, match.index))); |
| 346 | 344 |
| 347 let startIndex = match.index + match[0].length; | 345 let startIndex = match.index + match[0].length; |
| 348 let content = parseSelectorContent(selector, startIndex); | 346 let content = parseSelectorContent(selector, startIndex); |
| 349 if (!content) | 347 if (!content) |
| 350 { | 348 { |
| 351 this.window.console.error( | 349 console.error(new SyntaxError("Failed to parse Adblock Plus " + |
| 352 new SyntaxError("Failed to parse Adblock Plus " + | 350 `selector ${selector} ` + |
| 353 `selector ${selector} ` + | 351 "due to unmatched parentheses.")); |
| 354 "due to unmatched parentheses.")); | |
| 355 return null; | 352 return null; |
| 356 } | 353 } |
| 357 if (match[1] == "properties") | 354 if (match[1] == "properties") |
| 358 selectors.push(new PropsSelector(content.text)); | 355 selectors.push(new PropsSelector(content.text)); |
| 359 else if (match[1] == "has") | 356 else if (match[1] == "has") |
| 360 { | 357 { |
| 361 let hasSelectors = this.parseSelector(content.text); | 358 let hasSelectors = this.parseSelector(content.text); |
| 362 if (hasSelectors == null) | 359 if (hasSelectors == null) |
| 363 return null; | 360 return null; |
| 364 selectors.push(new HasSelector(hasSelectors)); | 361 selectors.push(new HasSelector(hasSelectors)); |
| 365 } | 362 } |
| 366 else if (match[1] == "contains") | 363 else if (match[1] == "contains") |
| 367 selectors.push(new ContainsSelector(content.text)); | 364 selectors.push(new ContainsSelector(content.text)); |
| 368 else | 365 else |
| 369 { | 366 { |
| 370 // this is an error, can't parse selector. | 367 // this is an error, can't parse selector. |
| 371 this.window.console.error( | 368 console.error(new SyntaxError("Failed to parse Adblock Plus " + |
| 372 new SyntaxError("Failed to parse Adblock Plus " + | 369 `selector ${selector}, invalid ` + |
| 373 `selector ${selector}, invalid ` + | 370 `pseudo-class :-abp-${match[1]}().`)); |
| 374 `pseudo-class :-abp-${match[1]}().`)); | |
| 375 return null; | 371 return null; |
| 376 } | 372 } |
| 377 | 373 |
| 378 let suffix = this.parseSelector(selector.substr(content.end + 1)); | 374 let suffix = this.parseSelector(selector.substr(content.end + 1)); |
| 379 if (suffix == null) | 375 if (suffix == null) |
| 380 return null; | 376 return null; |
| 381 | 377 |
| 382 selectors.push(...suffix); | 378 selectors.push(...suffix); |
| 383 | 379 |
| 384 if (selectors.length == 1 && selectors[0] instanceof ContainsSelector) | 380 if (selectors.length == 1 && selectors[0] instanceof ContainsSelector) |
| 385 { | 381 { |
| 386 this.window.console.error( | 382 console.error(new SyntaxError("Failed to parse Adblock Plus " + |
| 387 new SyntaxError("Failed to parse Adblock Plus " + | 383 `selector ${selector}, can't ` + |
| 388 `selector ${selector}, can't ` + | 384 "have a lonely :-abp-contains().")); |
| 389 "have a lonely :-abp-contains().")); | |
| 390 return null; | 385 return null; |
| 391 } | 386 } |
| 392 return selectors; | 387 return selectors; |
| 393 }, | 388 }, |
| 394 | 389 |
| 395 /** | 390 /** |
| 396 * Processes the current document and applies all rules to it. | 391 * Processes the current document and applies all rules to it. |
| 397 * @param {CSSStyleSheet[]} [stylesheets] | 392 * @param {CSSStyleSheet[]} [stylesheets] |
| 398 * The list of new stylesheets that have been added to the document and | 393 * The list of new stylesheets that have been added to the document and |
| 399 * made reprocessing necessary. This parameter shouldn't be passed in for | 394 * made reprocessing necessary. This parameter shouldn't be passed in for |
| 400 * the initial processing, all of document's stylesheets will be considered | 395 * the initial processing, all of document's stylesheets will be considered |
| 401 * then and all rules, including the ones not dependent on styles. | 396 * then and all rules, including the ones not dependent on styles. |
| 402 * @param {function} [done] | 397 * @param {function} [done] |
| 403 * Callback to call when done. | 398 * Callback to call when done. |
| 404 */ | 399 */ |
| 405 _addSelectors(stylesheets, done) | 400 _addSelectors(stylesheets, done) |
| 406 { | 401 { |
| 407 let selectors = []; | 402 let selectors = []; |
| 408 let selectorFilters = []; | 403 let selectorFilters = []; |
| 409 | 404 |
| 410 let elements = []; | 405 let elements = []; |
| 411 let elementFilters = []; | 406 let elementFilters = []; |
| 412 | 407 |
| 413 let cssStyles = []; | 408 let cssStyles = []; |
| 414 | 409 |
| 415 let stylesheetOnlyChange = !!stylesheets; | 410 let stylesheetOnlyChange = !!stylesheets; |
| 416 if (!stylesheets) | 411 if (!stylesheets) |
| 417 stylesheets = this.window.document.styleSheets; | 412 stylesheets = this.document.styleSheets; |
| 418 | 413 |
| 419 // Chrome < 51 doesn't have an iterable StyleSheetList | 414 // Chrome < 51 doesn't have an iterable StyleSheetList |
| 420 // https://issues.adblockplus.org/ticket/5381 | 415 // https://issues.adblockplus.org/ticket/5381 |
| 421 for (let i = 0; i < stylesheets.length; i++) | 416 for (let i = 0; i < stylesheets.length; i++) |
| 422 { | 417 { |
| 423 let stylesheet = stylesheets[i]; | 418 let stylesheet = stylesheets[i]; |
| 424 // Explicitly ignore third-party stylesheets to ensure consistent behavior | 419 // Explicitly ignore third-party stylesheets to ensure consistent behavior |
| 425 // between Firefox and Chrome. | 420 // between Firefox and Chrome. |
| 426 if (!this.isSameOrigin(stylesheet)) | 421 if (!this.isSameOrigin(stylesheet)) |
| 427 continue; | 422 continue; |
| 428 | 423 |
| 429 let rules = stylesheet.cssRules; | 424 let rules = stylesheet.cssRules; |
| 430 if (!rules) | 425 if (!rules) |
| 431 continue; | 426 continue; |
| 432 | 427 |
| 433 // Chrome < 51 doesn't have an iterable CSSRuleList | 428 // Chrome < 51 doesn't have an iterable CSSRuleList |
| 434 // https://issues.adblockplus.org/ticket/5773 | 429 // https://issues.adblockplus.org/ticket/5773 |
| 435 for (let j = 0; j < rules.length; j++) | 430 for (let j = 0; j < rules.length; j++) |
| 436 { | 431 { |
| 437 let rule = rules[j]; | 432 let rule = rules[j]; |
| 438 if (rule.type != rule.STYLE_RULE) | 433 if (rule.type != rule.STYLE_RULE) |
| 439 continue; | 434 continue; |
| 440 | 435 |
| 441 cssStyles.push(stringifyStyle(rule)); | 436 cssStyles.push(stringifyStyle(rule)); |
| 442 } | 437 } |
| 443 } | 438 } |
| 444 | 439 |
| 445 let {document} = this.window; | |
| 446 | |
| 447 let patterns = this.patterns.slice(); | 440 let patterns = this.patterns.slice(); |
| 448 let pattern = null; | 441 let pattern = null; |
| 449 let generator = null; | 442 let generator = null; |
| 450 | 443 |
| 451 let processPatterns = () => | 444 let processPatterns = () => |
| 452 { | 445 { |
| 453 let cycleStart = this.window.performance.now(); | 446 let cycleStart = performance.now(); |
| 454 | 447 |
| 455 if (!pattern) | 448 if (!pattern) |
| 456 { | 449 { |
| 457 if (!patterns.length) | 450 if (!patterns.length) |
| 458 { | 451 { |
| 459 this.addSelectorsFunc(selectors, selectorFilters); | 452 this.addSelectorsFunc(selectors, selectorFilters); |
| 460 this.hideElemsFunc(elements, elementFilters); | 453 this.hideElemsFunc(elements, elementFilters); |
| 461 if (typeof done == "function") | 454 if (typeof done == "function") |
| 462 done(); | 455 done(); |
| 463 return; | 456 return; |
| 464 } | 457 } |
| 465 | 458 |
| 466 pattern = patterns.shift(); | 459 pattern = patterns.shift(); |
| 467 | 460 |
| 468 if (stylesheetOnlyChange && | 461 if (stylesheetOnlyChange && |
| 469 !pattern.selectors.some(selector => selector.dependsOnStyles)) | 462 !pattern.selectors.some(selector => selector.dependsOnStyles)) |
| 470 { | 463 { |
| 471 pattern = null; | 464 pattern = null; |
| 472 return processPatterns(); | 465 return processPatterns(); |
| 473 } | 466 } |
| 474 generator = evaluate(pattern.selectors, 0, "", document, cssStyles); | 467 generator = evaluate(pattern.selectors, 0, "", |
| 468 this.document, cssStyles); |
| 475 } | 469 } |
| 476 for (let selector of generator) | 470 for (let selector of generator) |
| 477 { | 471 { |
| 478 if (selector != null) | 472 if (selector != null) |
| 479 { | 473 { |
| 480 if (isSelectorHidingOnlyPattern(pattern)) | 474 if (isSelectorHidingOnlyPattern(pattern)) |
| 481 { | 475 { |
| 482 selectors.push(selector); | 476 selectors.push(selector); |
| 483 selectorFilters.push(pattern.text); | 477 selectorFilters.push(pattern.text); |
| 484 } | 478 } |
| 485 else | 479 else |
| 486 { | 480 { |
| 487 for (let element of document.querySelectorAll(selector)) | 481 for (let element of this.document.querySelectorAll(selector)) |
| 488 { | 482 { |
| 489 elements.push(element); | 483 elements.push(element); |
| 490 elementFilters.push(pattern.text); | 484 elementFilters.push(pattern.text); |
| 491 } | 485 } |
| 492 } | 486 } |
| 493 } | 487 } |
| 494 if (this.window.performance.now() - | 488 if (performance.now() - cycleStart > MAX_SYNCHRONOUS_PROCESSING_TIME) |
| 495 cycleStart > MAX_SYNCHRONOUS_PROCESSING_TIME) | |
| 496 { | 489 { |
| 497 this.window.setTimeout(processPatterns, 0); | 490 setTimeout(processPatterns, 0); |
| 498 return; | 491 return; |
| 499 } | 492 } |
| 500 } | 493 } |
| 501 pattern = null; | 494 pattern = null; |
| 502 return processPatterns(); | 495 return processPatterns(); |
| 503 }; | 496 }; |
| 504 | 497 |
| 505 processPatterns(); | 498 processPatterns(); |
| 506 }, | 499 }, |
| 507 | 500 |
| (...skipping 16 matching lines...) Expand all Loading... |
| 524 /** | 517 /** |
| 525 * Re-run filtering either immediately or queued. | 518 * Re-run filtering either immediately or queued. |
| 526 * @param {CSSStyleSheet[]} [stylesheets] | 519 * @param {CSSStyleSheet[]} [stylesheets] |
| 527 * new stylesheets to be processed. This parameter should be omitted | 520 * new stylesheets to be processed. This parameter should be omitted |
| 528 * for DOM modification (full reprocessing required). | 521 * for DOM modification (full reprocessing required). |
| 529 */ | 522 */ |
| 530 queueFiltering(stylesheets) | 523 queueFiltering(stylesheets) |
| 531 { | 524 { |
| 532 let completion = () => | 525 let completion = () => |
| 533 { | 526 { |
| 534 this._lastInvocation = this.window.performance.now(); | 527 this._lastInvocation = performance.now(); |
| 535 this._filteringInProgress = false; | 528 this._filteringInProgress = false; |
| 536 if (this._scheduledProcessing) | 529 if (this._scheduledProcessing) |
| 537 { | 530 { |
| 538 let newStylesheets = this._scheduledProcessing.stylesheets; | 531 let newStylesheets = this._scheduledProcessing.stylesheets; |
| 539 this._scheduledProcessing = null; | 532 this._scheduledProcessing = null; |
| 540 this.queueFiltering(newStylesheets); | 533 this.queueFiltering(newStylesheets); |
| 541 } | 534 } |
| 542 }; | 535 }; |
| 543 | 536 |
| 544 if (this._scheduledProcessing) | 537 if (this._scheduledProcessing) |
| 545 { | 538 { |
| 546 if (!stylesheets) | 539 if (!stylesheets) |
| 547 this._scheduledProcessing.stylesheets = null; | 540 this._scheduledProcessing.stylesheets = null; |
| 548 else if (this._scheduledProcessing.stylesheets) | 541 else if (this._scheduledProcessing.stylesheets) |
| 549 this._scheduledProcessing.stylesheets.push(...stylesheets); | 542 this._scheduledProcessing.stylesheets.push(...stylesheets); |
| 550 } | 543 } |
| 551 else if (this._filteringInProgress) | 544 else if (this._filteringInProgress) |
| 552 { | 545 { |
| 553 this._scheduledProcessing = {stylesheets}; | 546 this._scheduledProcessing = {stylesheets}; |
| 554 } | 547 } |
| 555 else if (this.window.performance.now() - | 548 else if (performance.now() - this._lastInvocation < MIN_INVOCATION_INTERVAL) |
| 556 this._lastInvocation < MIN_INVOCATION_INTERVAL) | |
| 557 { | 549 { |
| 558 this._scheduledProcessing = {stylesheets}; | 550 this._scheduledProcessing = {stylesheets}; |
| 559 this.window.setTimeout(() => | 551 setTimeout(() => |
| 560 { | 552 { |
| 561 let newStylesheets = this._scheduledProcessing.stylesheets; | 553 let newStylesheets = this._scheduledProcessing.stylesheets; |
| 562 this._filteringInProgress = true; | 554 this._filteringInProgress = true; |
| 563 this._scheduledProcessing = null; | 555 this._scheduledProcessing = null; |
| 564 this._addSelectors(newStylesheets, completion); | 556 this._addSelectors(newStylesheets, completion); |
| 565 }, | 557 }, |
| 566 MIN_INVOCATION_INTERVAL - | 558 MIN_INVOCATION_INTERVAL - (performance.now() - this._lastInvocation)); |
| 567 (this.window.performance.now() - this._lastInvocation)); | |
| 568 } | 559 } |
| 569 else | 560 else |
| 570 { | 561 { |
| 571 this._filteringInProgress = true; | 562 this._filteringInProgress = true; |
| 572 this._addSelectors(stylesheets, completion); | 563 this._addSelectors(stylesheets, completion); |
| 573 } | 564 } |
| 574 }, | 565 }, |
| 575 | 566 |
| 576 onLoad(event) | 567 onLoad(event) |
| 577 { | 568 { |
| 578 let stylesheet = event.target.sheet; | 569 let stylesheet = event.target.sheet; |
| 579 if (stylesheet) | 570 if (stylesheet) |
| 580 this.queueFiltering([stylesheet]); | 571 this.queueFiltering([stylesheet]); |
| 581 }, | 572 }, |
| 582 | 573 |
| 583 observe(mutations) | 574 observe(mutations) |
| 584 { | 575 { |
| 585 this.queueFiltering(); | 576 this.queueFiltering(); |
| 586 }, | 577 }, |
| 587 | 578 |
| 588 apply() | 579 apply(patterns) |
| 589 { | 580 { |
| 590 this.getFiltersFunc(patterns => | 581 this.patterns = []; |
| 582 for (let pattern of patterns) |
| 591 { | 583 { |
| 592 this.patterns = []; | 584 let selectors = this.parseSelector(pattern.selector); |
| 593 for (let pattern of patterns) | 585 if (selectors != null && selectors.length > 0) |
| 594 { | 586 this.patterns.push({selectors, text: pattern.text}); |
| 595 let selectors = this.parseSelector(pattern.selector); | 587 } |
| 596 if (selectors != null && selectors.length > 0) | |
| 597 this.patterns.push({selectors, text: pattern.text}); | |
| 598 } | |
| 599 | 588 |
| 600 if (this.patterns.length > 0) | 589 if (this.patterns.length > 0) |
| 601 { | 590 { |
| 602 let {document} = this.window; | 591 this.queueFiltering(); |
| 603 this.queueFiltering(); | 592 this.observer.observe( |
| 604 this.observer.observe( | 593 this.document, |
| 605 document, | 594 { |
| 606 { | 595 childList: true, |
| 607 childList: true, | 596 attributes: true, |
| 608 attributes: true, | 597 characterData: true, |
| 609 characterData: true, | 598 subtree: true |
| 610 subtree: true | 599 } |
| 611 } | 600 ); |
| 612 ); | 601 this.document.addEventListener("load", this.onLoad.bind(this), true); |
| 613 document.addEventListener("load", this.onLoad.bind(this), true); | 602 } |
| 614 } | |
| 615 }); | |
| 616 } | 603 } |
| 617 }; | 604 }; |
| 618 | 605 |
| 619 exports.ElemHideEmulation = ElemHideEmulation; | 606 exports.ElemHideEmulation = ElemHideEmulation; |
| OLD | NEW |