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 |