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 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
71 | 71 |
72 for (let name of list) | 72 for (let name of list) |
73 { | 73 { |
74 if (name.length > suffixLength && name.slice(-suffixLength) == "." + domain) | 74 if (name.length > suffixLength && name.slice(-suffixLength) == "." + domain) |
75 subdomains.push(name.slice(0, -suffixLength)); | 75 subdomains.push(name.slice(0, -suffixLength)); |
76 } | 76 } |
77 | 77 |
78 return subdomains; | 78 return subdomains; |
79 } | 79 } |
80 | 80 |
| 81 function extractFilterDomains(filters) |
| 82 { |
| 83 let domains = new Set(); |
| 84 for (let filter of filters) |
| 85 { |
| 86 let parsed = parseFilterRegexpSource(filter.regexpSource); |
| 87 if (parsed.justHostname) |
| 88 domains.add(parsed.hostname); |
| 89 } |
| 90 return domains; |
| 91 } |
| 92 |
81 function convertElemHideFilter(filter, elemhideSelectorExceptions) | 93 function convertElemHideFilter(filter, elemhideSelectorExceptions) |
82 { | 94 { |
83 let included = []; | 95 let included = []; |
84 let excluded = []; | 96 let excluded = []; |
85 let rules = []; | 97 let rules = []; |
86 | 98 |
87 parseDomains(filter.domains, included, excluded); | 99 parseDomains(filter.domains, included, excluded); |
88 | 100 |
89 if (excluded.length == 0 && !(filter.selector in elemhideSelectorExceptions)) | 101 if (excluded.length == 0 && !(filter.selector in elemhideSelectorExceptions)) |
90 return {matchDomains: included.map(matchDomain), selector: filter.selector}; | 102 return {matchDomains: included.map(matchDomain), selector: filter.selector}; |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
135 // If we're currently inside the hostname we have to be careful not to | 147 // If we're currently inside the hostname we have to be careful not to |
136 // escape any characters until after we have converted it to punycode. | 148 // escape any characters until after we have converted it to punycode. |
137 if (hostnameStart != null && !hostnameFinished) | 149 if (hostnameStart != null && !hostnameFinished) |
138 { | 150 { |
139 let endingChar = (c == "*" || c == "^" || | 151 let endingChar = (c == "*" || c == "^" || |
140 c == "?" || c == "/" || c == "|"); | 152 c == "?" || c == "/" || c == "|"); |
141 if (!endingChar && i != lastIndex) | 153 if (!endingChar && i != lastIndex) |
142 continue; | 154 continue; |
143 | 155 |
144 hostname = punycode.toASCII( | 156 hostname = punycode.toASCII( |
145 characters.slice(hostnameStart, endingChar ? i : i + 1).join("") | 157 characters.slice(hostnameStart, endingChar ? i : i + 1).join("") |
| 158 .toLowerCase() |
146 ); | 159 ); |
147 hostnameFinished = justHostname = true; | 160 hostnameFinished = justHostname = true; |
148 regexp.push(escapeRegExp(hostname)); | 161 regexp.push(escapeRegExp(hostname)); |
149 if (!endingChar) | 162 if (!endingChar) |
150 break; | 163 break; |
151 } | 164 } |
152 | 165 |
153 switch (c) | 166 switch (c) |
154 { | 167 { |
155 case "*": | 168 case "*": |
(...skipping 241 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
397 { | 410 { |
398 newSelector.push(selector.substring(i, pos.start)); | 411 newSelector.push(selector.substring(i, pos.start)); |
399 newSelector.push('[id=', selector.substring(pos.start + 1, pos.end), ']'); | 412 newSelector.push('[id=', selector.substring(pos.start + 1, pos.end), ']'); |
400 i = pos.end; | 413 i = pos.end; |
401 } | 414 } |
402 newSelector.push(selector.substring(i)); | 415 newSelector.push(selector.substring(i)); |
403 | 416 |
404 return newSelector.join(""); | 417 return newSelector.join(""); |
405 } | 418 } |
406 | 419 |
407 function addCSSRules(rules, selectors, matchDomain) | 420 function addCSSRules(rules, selectors, matchDomain, exceptionDomains) |
408 { | 421 { |
| 422 let unlessDomain = exceptionDomains.size > 0 ? [] : null; |
| 423 |
| 424 exceptionDomains.forEach(name => unlessDomain.push("*" + name)); |
| 425 |
409 while (selectors.length) | 426 while (selectors.length) |
410 { | 427 { |
411 let selector = selectors.splice(0, selectorLimit).join(", "); | 428 let selector = selectors.splice(0, selectorLimit).join(", "); |
412 | 429 |
413 // As of Safari 9.0 element IDs are matched as lowercase. We work around | 430 // As of Safari 9.0 element IDs are matched as lowercase. We work around |
414 // this by converting to the attribute format [id="elementID"] | 431 // this by converting to the attribute format [id="elementID"] |
415 selector = convertIDSelectorsToAttributeSelectors(selector); | 432 selector = convertIDSelectorsToAttributeSelectors(selector); |
416 | 433 |
417 rules.push({ | 434 let rule = { |
418 trigger: {"url-filter": matchDomain, | 435 trigger: {"url-filter": matchDomain, |
419 "url-filter-is-case-sensitive": true}, | 436 "url-filter-is-case-sensitive": true}, |
420 action: {type: "css-display-none", | 437 action: {type: "css-display-none", |
421 selector: selector} | 438 selector: selector} |
422 }); | 439 }; |
| 440 |
| 441 if (unlessDomain) |
| 442 rule.trigger["unless-domain"] = unlessDomain; |
| 443 |
| 444 rules.push(rule); |
423 } | 445 } |
424 } | 446 } |
425 | 447 |
426 let ContentBlockerList = | 448 let ContentBlockerList = |
427 /** | 449 /** |
428 * Create a new Adblock Plus filter to content blocker list converter | 450 * Create a new Adblock Plus filter to content blocker list converter |
429 * | 451 * |
430 * @constructor | 452 * @constructor |
431 */ | 453 */ |
432 exports.ContentBlockerList = function () | 454 exports.ContentBlockerList = function () |
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
509 { | 531 { |
510 for (let matchDomain of result.matchDomains) | 532 for (let matchDomain of result.matchDomains) |
511 { | 533 { |
512 let group = groupedElemhideFilters.get(matchDomain) || []; | 534 let group = groupedElemhideFilters.get(matchDomain) || []; |
513 group.push(result.selector); | 535 group.push(result.selector); |
514 groupedElemhideFilters.set(matchDomain, group); | 536 groupedElemhideFilters.set(matchDomain, group); |
515 } | 537 } |
516 } | 538 } |
517 } | 539 } |
518 | 540 |
519 addCSSRules(rules, genericSelectors, "^https?://"); | 541 // Separate out the element hiding exceptions that have only a hostname part |
| 542 // from the rest. This allows us to implement a workaround for issue #5345 |
| 543 // (WebKit bug #167423), but as a bonus it also reduces the number of |
| 544 // generated rules. The downside is that the exception will only apply to the |
| 545 // top-level document, not to iframes. We have to live with this until the |
| 546 // WebKit bug is fixed in all supported versions of Safari. |
| 547 // https://bugs.webkit.org/show_bug.cgi?id=167423 |
| 548 // |
| 549 // Note that as a result of this workaround we end up with a huge rule set in |
| 550 // terms of the amount of memory used. This can cause Node.js to throw |
| 551 // "JavaScript heap out of memory". To avoid this, call Node.js with |
| 552 // --max_old_space_size=4096 |
| 553 let elemhideExceptionDomains = extractFilterDomains(this.elemhideExceptions); |
520 | 554 |
521 // Right after the generic element hiding filters, add the exceptions that | 555 let genericSelectorExceptionDomains = |
522 // should apply only to those filters. | 556 extractFilterDomains(this.generichideExceptions); |
523 for (let filter of this.generichideExceptions) | 557 elemhideExceptionDomains.forEach(name => |
524 convertFilterAddRules(rules, filter, "ignore-previous-rules", false); | 558 { |
| 559 genericSelectorExceptionDomains.add(name); |
| 560 }); |
| 561 |
| 562 addCSSRules(rules, genericSelectors, "^https?://", |
| 563 genericSelectorExceptionDomains); |
525 | 564 |
526 groupedElemhideFilters.forEach((selectors, matchDomain) => | 565 groupedElemhideFilters.forEach((selectors, matchDomain) => |
527 { | 566 { |
528 addCSSRules(rules, selectors, matchDomain); | 567 addCSSRules(rules, selectors, matchDomain, elemhideExceptionDomains); |
529 }); | 568 }); |
530 | 569 |
531 for (let filter of this.elemhideExceptions) | |
532 convertFilterAddRules(rules, filter, "ignore-previous-rules", false); | |
533 | |
534 let requestFilterExceptionDomains = []; | 570 let requestFilterExceptionDomains = []; |
535 for (let filter of this.genericblockExceptions) | 571 for (let filter of this.genericblockExceptions) |
536 { | 572 { |
537 let parsed = parseFilterRegexpSource(filter.regexpSource); | 573 let parsed = parseFilterRegexpSource(filter.regexpSource); |
538 if (parsed.hostname) | 574 if (parsed.hostname) |
539 requestFilterExceptionDomains.push(parsed.hostname); | 575 requestFilterExceptionDomains.push(parsed.hostname); |
540 } | 576 } |
541 | 577 |
542 for (let filter of this.requestFilters) | 578 for (let filter of this.requestFilters) |
543 { | 579 { |
544 convertFilterAddRules(rules, filter, "block", true, | 580 convertFilterAddRules(rules, filter, "block", true, |
545 requestFilterExceptionDomains); | 581 requestFilterExceptionDomains); |
546 } | 582 } |
547 | 583 |
548 for (let filter of this.requestExceptions) | 584 for (let filter of this.requestExceptions) |
549 convertFilterAddRules(rules, filter, "ignore-previous-rules", true); | 585 convertFilterAddRules(rules, filter, "ignore-previous-rules", true); |
550 | 586 |
551 return rules; | 587 return rules; |
552 }; | 588 }; |
OLD | NEW |