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 |