| Left: | ||
| Right: | 
| 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-2017 eyeo GmbH | 3 * Copyright (C) 2006-2017 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 139 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 150 let priority = rule.style.getPropertyPriority(property); | 150 let priority = rule.style.getPropertyPriority(property); | 
| 151 styles.push(`${property}: ${value}${priority ? " !" + priority : ""};`); | 151 styles.push(`${property}: ${value}${priority ? " !" + priority : ""};`); | 
| 152 } | 152 } | 
| 153 styles.sort(); | 153 styles.sort(); | 
| 154 return { | 154 return { | 
| 155 style: styles.join(" "), | 155 style: styles.join(" "), | 
| 156 subSelectors: splitSelector(rule.selectorText) | 156 subSelectors: splitSelector(rule.selectorText) | 
| 157 }; | 157 }; | 
| 158 } | 158 } | 
| 159 | 159 | 
| 160 function* evaluate(chain, index, prefix, subtree, styles) | 160 function* evaluate(chain, index, prefix, subtree, styles, map) | 
| 161 { | 161 { | 
| 162 if (index >= chain.length) | 162 if (index >= chain.length) | 
| 163 { | 163 { | 
| 164 yield prefix; | 164 yield prefix; | 
| 165 return; | 165 return; | 
| 166 } | 166 } | 
| 167 for (let [selector, element] of | 167 for (let [selector, element] of | 
| 168 chain[index].getSelectors(prefix, subtree, styles)) | 168 chain[index].getSelectors(prefix, subtree, styles, | 
| 169 yield* evaluate(chain, index + 1, selector, element, styles); | 169 chain.slice(index), map)) | 
| 170 yield* evaluate(chain, index + 1, selector, element, styles, map); | |
| 170 } | 171 } | 
| 171 | 172 | 
| 172 function PlainSelector(selector) | 173 function PlainSelector(selector) | 
| 173 { | 174 { | 
| 174 this._selector = selector; | 175 this._selector = selector; | 
| 175 } | 176 } | 
| 176 | 177 | 
| 177 PlainSelector.prototype = { | 178 PlainSelector.prototype = { | 
| 178 /** | 179 /** | 
| 179 * Generator function returning a pair of selector | 180 * Generator function returning a pair of selector | 
| (...skipping 17 matching lines...) Expand all Loading... | |
| 197 } | 198 } | 
| 198 | 199 | 
| 199 HasSelector.prototype = { | 200 HasSelector.prototype = { | 
| 200 requiresHiding: true, | 201 requiresHiding: true, | 
| 201 | 202 | 
| 202 get dependsOnStyles() | 203 get dependsOnStyles() | 
| 203 { | 204 { | 
| 204 return this._innerSelectors.some(selector => selector.dependsOnStyles); | 205 return this._innerSelectors.some(selector => selector.dependsOnStyles); | 
| 205 }, | 206 }, | 
| 206 | 207 | 
| 207 *getSelectors(prefix, subtree, styles) | 208 *getSelectors(prefix, subtree, styles, chain, map) | 
| 208 { | 209 { | 
| 209 for (let element of this.getElements(prefix, subtree, styles)) | 210 for (let element of this.getElements(prefix, subtree, styles, chain, map)) | 
| 210 yield [makeSelector(element, ""), element]; | 211 yield [makeSelector(element, ""), element]; | 
| 211 }, | 212 }, | 
| 212 | 213 | 
| 213 /** | 214 /** | 
| 214 * Generator function returning selected elements. | 215 * Generator function returning selected elements. | 
| 215 * @param {string} prefix the prefix for the selector. | 216 * @param {string} prefix the prefix for the selector. | 
| 216 * @param {Node} subtree the subtree we work on. | 217 * @param {Node} subtree the subtree we work on. | 
| 217 * @param {StringifiedStyle[]} styles the stringified style objects. | 218 * @param {StringifiedStyle[]} styles the stringified style objects. | 
| 219 * @param {Array} chain the chain of selectors including this. | |
| 220 * @param {WeakMap} map of the elements and chain for re-evaluation. | |
| 218 */ | 221 */ | 
| 219 *getElements(prefix, subtree, styles) | 222 *getElements(prefix, subtree, styles, chain, map) | 
| 220 { | 223 { | 
| 221 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? | 224 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? | 
| 222 prefix + "*" : prefix; | 225 prefix + "*" : prefix; | 
| 223 let elements = subtree.querySelectorAll(actualPrefix); | 226 let elements = subtree.querySelectorAll(actualPrefix); | 
| 224 for (let element of elements) | 227 for (let element of elements) | 
| 225 { | 228 { | 
| 226 let iter = evaluate(this._innerSelectors, 0, "", element, styles); | 229 let e = map.get(element); | 
| 230 if (e == undefined) | |
| 231 map.set(element, [chain]); | |
| 232 else | |
| 233 e.push(chain); | |
| 234 let iter = evaluate(this._innerSelectors, 0, "", element, styles, map); | |
| 227 for (let selector of iter) | 235 for (let selector of iter) | 
| 228 { | 236 { | 
| 229 if (relativeSelector.test(selector)) | 237 if (relativeSelector.test(selector)) | 
| 230 selector = ":scope" + selector; | 238 selector = ":scope" + selector; | 
| 231 if (element.querySelector(selector)) | 239 if (element.querySelector(selector)) | 
| 232 yield element; | 240 yield element; | 
| 233 } | 241 } | 
| 234 } | 242 } | 
| 235 } | 243 } | 
| 236 }; | 244 }; | 
| 237 | 245 | 
| 238 function ContainsSelector(textContent) | 246 function ContainsSelector(textContent) | 
| 239 { | 247 { | 
| 240 this._text = textContent; | 248 this._text = textContent; | 
| 241 } | 249 } | 
| 242 | 250 | 
| 243 ContainsSelector.prototype = { | 251 ContainsSelector.prototype = { | 
| 244 requiresHiding: true, | 252 requiresHiding: true, | 
| 245 | 253 | 
| 246 *getSelectors(prefix, subtree, stylesheet) | 254 *getSelectors(prefix, subtree, stylesheet, chain, map) | 
| 247 { | 255 { | 
| 248 for (let element of this.getElements(prefix, subtree, stylesheet)) | 256 for (let element of this.getElements(prefix, subtree, stylesheet, | 
| 257 chain, map)) | |
| 249 yield [makeSelector(element, ""), subtree]; | 258 yield [makeSelector(element, ""), subtree]; | 
| 250 }, | 259 }, | 
| 251 | 260 | 
| 252 *getElements(prefix, subtree, stylesheet) | 261 *getElements(prefix, subtree, stylesheet, chain, map) | 
| 253 { | 262 { | 
| 254 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? | 263 let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ? | 
| 255 prefix + "*" : prefix; | 264 prefix + "*" : prefix; | 
| 256 let elements = subtree.querySelectorAll(actualPrefix); | 265 let elements = subtree.querySelectorAll(actualPrefix); | 
| 257 for (let element of elements) | 266 for (let element of elements) | 
| 258 if (element.textContent.includes(this._text)) | 267 if (element.textContent.includes(this._text)) | 
| 259 yield element; | 268 yield element; | 
| 260 } | 269 } | 
| 261 }; | 270 }; | 
| 262 | 271 | 
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 303 } | 312 } | 
| 304 }, | 313 }, | 
| 305 | 314 | 
| 306 *getSelectors(prefix, subtree, styles) | 315 *getSelectors(prefix, subtree, styles) | 
| 307 { | 316 { | 
| 308 for (let selector of this.findPropsSelectors(styles, prefix, this._regexp)) | 317 for (let selector of this.findPropsSelectors(styles, prefix, this._regexp)) | 
| 309 yield [selector, subtree]; | 318 yield [selector, subtree]; | 
| 310 } | 319 } | 
| 311 }; | 320 }; | 
| 312 | 321 | 
| 322 function isSelectorHidingOnlyPattern(pattern) | |
| 323 { | |
| 324 return pattern.selectors.some(s => s.preferHideWithSelector) && | |
| 325 !pattern.selectors.some(s => s.requiresHiding); | |
| 326 } | |
| 327 | |
| 313 function ElemHideEmulation(window, getFiltersFunc, addSelectorsFunc, | 328 function ElemHideEmulation(window, getFiltersFunc, addSelectorsFunc, | 
| 314 hideElemsFunc) | 329 hideElemsFunc) | 
| 315 { | 330 { | 
| 316 this.window = window; | 331 this.window = window; | 
| 317 this.getFiltersFunc = getFiltersFunc; | 332 this.getFiltersFunc = getFiltersFunc; | 
| 318 this.addSelectorsFunc = addSelectorsFunc; | 333 this.addSelectorsFunc = addSelectorsFunc; | 
| 319 this.hideElemsFunc = hideElemsFunc; | 334 this.hideElemsFunc = hideElemsFunc; | 
| 335 this.observer = new window.MutationObserver(this.observe.bind(this)); | |
| 320 } | 336 } | 
| 321 | 337 | 
| 322 ElemHideEmulation.prototype = { | 338 ElemHideEmulation.prototype = { | 
| 323 isSameOrigin(stylesheet) | 339 isSameOrigin(stylesheet) | 
| 324 { | 340 { | 
| 325 try | 341 try | 
| 326 { | 342 { | 
| 327 return new URL(stylesheet.href).origin == this.window.location.origin; | 343 return new URL(stylesheet.href).origin == this.window.location.origin; | 
| 328 } | 344 } | 
| 329 catch (e) | 345 catch (e) | 
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 396 { | 412 { | 
| 397 this.window.console.error( | 413 this.window.console.error( | 
| 398 new SyntaxError("Failed to parse Adblock Plus " + | 414 new SyntaxError("Failed to parse Adblock Plus " + | 
| 399 `selector ${selector}, can't ` + | 415 `selector ${selector}, can't ` + | 
| 400 "have a lonely :-abp-contains().")); | 416 "have a lonely :-abp-contains().")); | 
| 401 return null; | 417 return null; | 
| 402 } | 418 } | 
| 403 return selectors; | 419 return selectors; | 
| 404 }, | 420 }, | 
| 405 | 421 | 
| 422 _observerMap: new WeakMap(), | |
| 423 | |
| 406 _lastInvocation: 0, | 424 _lastInvocation: 0, | 
| 407 | 425 | 
| 408 /** | 426 /** | 
| 409 * Processes the current document and applies all rules to it. | 427 * Processes the current document and applies all rules to it. | 
| 410 * @param {CSSStyleSheet[]} [stylesheets] | 428 * @param {CSSStyleSheet[]} [stylesheets] | 
| 411 * The list of new stylesheets that have been added to the document and | 429 * The list of new stylesheets that have been added to the document and | 
| 412 * made reprocessing necessary. This parameter shouldn't be passed in for | 430 * made reprocessing necessary. This parameter shouldn't be passed in for | 
| 413 * the initial processing, all of document's stylesheets will be considered | 431 * the initial processing, all of document's stylesheets will be considered | 
| 414 * then and all rules, including the ones not dependent on styles. | 432 * then and all rules, including the ones not dependent on styles. | 
| 433 * @param {boolean} [domUpdate] | |
| 434 * Indicate this is a DOM update. | |
| 415 */ | 435 */ | 
| 416 addSelectors(stylesheets) | 436 addSelectors(stylesheets, domUpdate) | 
| 417 { | 437 { | 
| 418 this._lastInvocation = Date.now(); | 438 this._lastInvocation = Date.now(); | 
| 419 | 439 | 
| 420 let selectors = []; | 440 let selectors = []; | 
| 421 let selectorFilters = []; | 441 let selectorFilters = []; | 
| 422 | 442 | 
| 423 let elements = []; | 443 let elements = []; | 
| 424 let elementFilters = []; | 444 let elementFilters = []; | 
| 425 | 445 | 
| 426 let cssStyles = []; | 446 let cssStyles = []; | 
| 427 | 447 | 
| 428 let stylesheetOnlyChange = !!stylesheets; | 448 let stylesheetOnlyChange = !!stylesheets && !domUpdate; | 
| 429 if (!stylesheets) | 449 if (!stylesheets) | 
| 430 stylesheets = this.window.document.styleSheets; | 450 stylesheets = this.window.document.styleSheets; | 
| 431 | 451 | 
| 432 // Chrome < 51 doesn't have an iterable StyleSheetList | 452 // Chrome < 51 doesn't have an iterable StyleSheetList | 
| 433 // https://issues.adblockplus.org/ticket/5381 | 453 // https://issues.adblockplus.org/ticket/5381 | 
| 434 for (let i = 0; i < stylesheets.length; i++) | 454 for (let i = 0; i < stylesheets.length; i++) | 
| 435 { | 455 { | 
| 436 let stylesheet = stylesheets[i]; | 456 let stylesheet = stylesheets[i]; | 
| 437 // Explicitly ignore third-party stylesheets to ensure consistent behavior | 457 // Explicitly ignore third-party stylesheets to ensure consistent behavior | 
| 438 // between Firefox and Chrome. | 458 // between Firefox and Chrome. | 
| (...skipping 15 matching lines...) Expand all Loading... | |
| 454 | 474 | 
| 455 let {document} = this.window; | 475 let {document} = this.window; | 
| 456 for (let pattern of this.patterns) | 476 for (let pattern of this.patterns) | 
| 457 { | 477 { | 
| 458 if (stylesheetOnlyChange && | 478 if (stylesheetOnlyChange && | 
| 459 !pattern.selectors.some(selector => selector.dependsOnStyles)) | 479 !pattern.selectors.some(selector => selector.dependsOnStyles)) | 
| 460 { | 480 { | 
| 461 continue; | 481 continue; | 
| 462 } | 482 } | 
| 463 | 483 | 
| 464 for (let selector of evaluate(pattern.selectors, | 484 for (let selector of evaluate(pattern.selectors, 0, "", document, | 
| 465 0, "", document, cssStyles)) | 485 cssStyles, this._observerMap)) | 
| 466 { | 486 { | 
| 467 if (pattern.selectors.some(s => s.preferHideWithSelector) && | 487 if (isSelectorHidingOnlyPattern(pattern)) | 
| 468 !pattern.selectors.some(s => s.requiresHiding)) | |
| 469 { | 488 { | 
| 470 selectors.push(selector); | 489 selectors.push(selector); | 
| 471 selectorFilters.push(pattern.text); | 490 selectorFilters.push(pattern.text); | 
| 472 } | 491 } | 
| 473 else | 492 else | 
| 474 { | 493 { | 
| 475 for (let element of document.querySelectorAll(selector)) | 494 for (let element of document.querySelectorAll(selector)) | 
| 476 { | 495 { | 
| 477 elements.push(element); | 496 elements.push(element); | 
| 478 elementFilters.push(pattern.text); | 497 elementFilters.push(pattern.text); | 
| 479 } | 498 } | 
| 480 } | 499 } | 
| 481 } | 500 } | 
| 482 } | 501 } | 
| 483 | 502 | 
| 484 this.addSelectorsFunc(selectors, selectorFilters); | 503 this.addSelectorsFunc(selectors, selectorFilters); | 
| 485 this.hideElemsFunc(elements, elementFilters); | 504 this.hideElemsFunc(elements, elementFilters); | 
| 486 }, | 505 }, | 
| 487 | 506 | 
| 488 _stylesheetQueue: null, | 507 _stylesheetQueue: null, | 
| 489 | 508 | 
| 509 /** Filtering reason | |
| 510 * @typedef {Object} FilteringReason | |
| 511 * @property {boolean} dom Indicate the DOM changed (tree or attributes) | |
| 512 * @property {CSSStyleSheet[]} [stylesheets] | |
| 513 * Indicate the stylesheets that needs refresh | |
| 514 * @property {WeakSet} subtrees The subtrees affected we were watching. | |
| 515 */ | |
| 516 | |
| 517 /** Re-run filtering either immediately or queued. | |
| 518 * @param {FilteringReason} reason why the filtering must be queued. | |
| 519 */ | |
| 520 queueFiltering(reason) | |
| 521 { | |
| 522 if (!this._stylesheetQueue && | |
| 523 (Date.now() - this._lastInvocation < MIN_INVOCATION_INTERVAL || | |
| 524 reason.dom)) | |
| 525 { | |
| 526 this._stylesheetQueue = []; | |
| 527 this.window.setTimeout(() => | |
| 528 { | |
| 529 let stylesheets = this._stylesheetQueue; | |
| 530 this._stylesheetQueue = null; | |
| 531 let domUpdate = reason.dom; | |
| 
 
hub
2017/08/03 16:28:18
I realise this should have been moved up, out of t
 
 | |
| 532 this.addSelectors(stylesheets, domUpdate); | |
| 
 
hub
2017/08/02 04:10:55
Here we don't use reason.subtree.
Actually I'm no
 
 | |
| 533 }, MIN_INVOCATION_INTERVAL - (Date.now() - this._lastInvocation)); | |
| 534 } | |
| 535 if (reason.stylesheets) | |
| 536 { | |
| 537 if (this._stylesheetQueue) | |
| 538 this._stylesheetQueue.push(...reason.stylesheets); | |
| 539 else | |
| 540 this.addSelectors(reason.stylesheets); | |
| 541 } | |
| 542 }, | |
| 543 | |
| 490 onLoad(event) | 544 onLoad(event) | 
| 491 { | 545 { | 
| 492 let stylesheet = event.target.sheet; | 546 let stylesheet = event.target.sheet; | 
| 493 if (stylesheet) | 547 if (stylesheet) | 
| 548 this.queueFiltering({stylesheets: [stylesheet]}); | |
| 549 }, | |
| 550 | |
| 551 observe(mutations) | |
| 552 { | |
| 553 let reason = {}; | |
| 554 reason.dom = true; | |
| 555 let stylesheets = []; | |
| 556 for (let mutation of mutations) | |
| 494 { | 557 { | 
| 495 if (!this._stylesheetQueue && | 558 if (mutation.type == "childList") | 
| 496 Date.now() - this._lastInvocation < MIN_INVOCATION_INTERVAL) | |
| 497 { | 559 { | 
| 498 this._stylesheetQueue = []; | 560 for (let added of mutation.addedNodes) | 
| 499 this.window.setTimeout(() => | |
| 500 { | 561 { | 
| 501 let stylesheets = this._stylesheetQueue; | 562 if (added.nodeType == Node.ELEMENT_NODE && | 
| 502 this._stylesheetQueue = null; | 563 (added.tagName == "STYLE" || added.tagName == "style") && | 
| 503 this.addSelectors(stylesheets); | 564 added.styesheet) | 
| 504 }, MIN_INVOCATION_INTERVAL - (Date.now() - this._lastInvocation)); | 565 stylesheets.push(added.stylesheet); | 
| 
 
hub
2017/08/02 04:10:54
If we have a new style element, then we likely hav
 
 | |
| 566 } | |
| 567 for (let removed of mutation.removedNodes) | |
| 568 { | |
| 569 this._observerMap.delete(removed); | |
| 570 } | |
| 505 } | 571 } | 
| 506 | 572 let currentNode = mutation.target; | 
| 507 if (this._stylesheetQueue) | 573 while (currentNode) | 
| 508 this._stylesheetQueue.push(stylesheet); | 574 { | 
| 509 else | 575 let e = this._observerMap.has(currentNode); | 
| 510 this.addSelectors([stylesheet]); | 576 if (e) | 
| 577 { | |
| 578 if (!(reason.subtrees instanceof Set)) | |
| 579 reason.subtrees = new Set(); | |
| 580 reason.subtrees.add(currentNode); | |
| 581 break; | |
| 582 } | |
| 583 currentNode = currentNode.parentNode; | |
| 584 } | |
| 511 } | 585 } | 
| 586 if (stylesheets.length > 0) | |
| 587 reason.stylesheets = stylesheets; | |
| 588 this.queueFiltering(reason); | |
| 512 }, | 589 }, | 
| 513 | 590 | 
| 514 apply() | 591 apply() | 
| 515 { | 592 { | 
| 516 this.getFiltersFunc(patterns => | 593 this.getFiltersFunc(patterns => | 
| 517 { | 594 { | 
| 518 this.patterns = []; | 595 this.patterns = []; | 
| 519 for (let pattern of patterns) | 596 for (let pattern of patterns) | 
| 520 { | 597 { | 
| 521 let selectors = this.parseSelector(pattern.selector); | 598 let selectors = this.parseSelector(pattern.selector); | 
| 522 if (selectors != null && selectors.length > 0) | 599 if (selectors != null && selectors.length > 0) | 
| 523 this.patterns.push({selectors, text: pattern.text}); | 600 this.patterns.push({selectors, text: pattern.text}); | 
| 524 } | 601 } | 
| 525 | 602 | 
| 526 if (this.patterns.length > 0) | 603 if (this.patterns.length > 0) | 
| 527 { | 604 { | 
| 528 let {document} = this.window; | 605 let {document} = this.window; | 
| 529 this.addSelectors(); | 606 this.addSelectors(); | 
| 607 this.observer.observe( | |
| 608 document, | |
| 609 { | |
| 610 childList: true, | |
| 611 attributes: true, | |
| 612 subtree: true, | |
| 613 attributeFilter: ["class", "id"] | |
| 
 
hub
2017/08/02 04:10:54
check for the obvious class and id attributes that
 
 | |
| 614 } | |
| 615 ); | |
| 530 document.addEventListener("load", this.onLoad.bind(this), true); | 616 document.addEventListener("load", this.onLoad.bind(this), true); | 
| 531 } | 617 } | 
| 532 }); | 618 }); | 
| 533 } | 619 } | 
| 534 }; | 620 }; | 
| OLD | NEW |