OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * This file is part of Adblock Plus <https://adblockplus.org/>, |
| 3 * Copyright (C) 2006-present eyeo GmbH |
| 4 * |
| 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 |
| 7 * published by the Free Software Foundation. |
| 8 * |
| 9 * Adblock Plus is distributed in the hope that it will be useful, |
| 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 * GNU General Public License for more details. |
| 13 * |
| 14 * You should have received a copy of the GNU General Public License |
| 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
| 16 */ |
| 17 |
| 18 "use strict"; |
| 19 |
| 20 /** |
| 21 * @fileOverview Filter text operations. |
| 22 */ |
| 23 |
| 24 // The memory optimizations in this file work as of V8 7.1.314. |
| 25 |
| 26 /** |
| 27 * The minimum length of a domain string for it to be considered long enough to |
| 28 * be eligible for optimization. |
| 29 * @type {number} |
| 30 */ |
| 31 const LONG_DOMAINS_THRESHOLD = 1000; |
| 32 |
| 33 /** |
| 34 * Cached strings. |
| 35 * @type {Map.<string,string>} |
| 36 */ |
| 37 let strings = new Map(); |
| 38 |
| 39 /** |
| 40 * Slices a string out of its internal parent string, thus allowing the memory |
| 41 * occupied by the parent string to be freed up. |
| 42 * |
| 43 * JavaScript engines like V8 tend to hold on to the parent of a sliced string |
| 44 * even if there are no references to the parent string. While this |
| 45 * optimization is enormously beneficial in general, in certain cases it has |
| 46 * the opposite effect of needlessly holding on to large amounts of unused |
| 47 * memory (V8 issue #2869). This operation creates an entirely new internal |
| 48 * string with its own copy of the original string's memory, thus allowing the |
| 49 * original string and any of its parents to be freed up. |
| 50 * |
| 51 * Note: This is a relatively expensive operation and should be used only when |
| 52 * the benefit outweighs the cost. |
| 53 * |
| 54 * @param {string} string The string to slice. |
| 55 * |
| 56 * @returns {string} An entirely new copy of the original string. |
| 57 */ |
| 58 function trueSlice(string) |
| 59 { |
| 60 return JSON.parse(JSON.stringify(string)); |
| 61 } |
| 62 |
| 63 /** |
| 64 * Optimizes content filter text. |
| 65 * |
| 66 * @param {string} text The filter text. |
| 67 * @param {?string} [domains] The domains part of the filter text. |
| 68 * @param {?string} [type] The type part of the filter text, either |
| 69 * <code>null</code> or <code>undefined</code>, or one of <code>@</code>, |
| 70 * <code>?</code>, and <code>$</code>. |
| 71 * @param {string} body The body part of the filter text. |
| 72 * |
| 73 * @returns {Array.<string>} An array containing the optimized filter text and |
| 74 * its optimized parts. |
| 75 */ |
| 76 function optimizeContentFilterText(text, domains, type, body) |
| 77 { |
| 78 // In EasyList+AA there are a handful of filters with very long domain |
| 79 // strings of 1,000 characters and even up to 100,000 characters. These tend |
| 80 // to take up a lot of memory. We can restructure the text here to optimize |
| 81 // for better memory usage on V8. |
| 82 if (domains && domains.length >= LONG_DOMAINS_THRESHOLD) |
| 83 { |
| 84 let copy = strings.get(domains); |
| 85 if (copy) |
| 86 { |
| 87 // Point to the cached copy. |
| 88 domains = copy; |
| 89 |
| 90 // V8 tends to hold on to the parent of a sliced string even if there are |
| 91 // no references to it. We must "slice" out these strings properly so the |
| 92 // original filter text is freed up. |
| 93 // https://bugs.chromium.org/p/v8/issues/detail?id=2869 |
| 94 if (typeof type == "string") |
| 95 type = trueSlice(type); |
| 96 body = trueSlice(body); |
| 97 |
| 98 // Reconstruct the text with an optimized layout. |
| 99 text = domains + "#" + (type || "") + "#" + body; |
| 100 } |
| 101 else |
| 102 { |
| 103 strings.set(domains, domains); |
| 104 } |
| 105 } |
| 106 |
| 107 return [text, domains, type, body]; |
| 108 } |
| 109 |
| 110 exports.optimizeContentFilterText = optimizeContentFilterText; |
| 111 |
| 112 /** |
| 113 * Optimizes blocking and whitelist filter text. |
| 114 * |
| 115 * @param {string} text The filter text. |
| 116 * @param {string} pattern The pattern part of the filter text. |
| 117 * @param {?string} [domains] The domains part of the filter text. |
| 118 * @param {?string} [sitekeys] The sitekeys part of the filter text. |
| 119 * @param {?string} [csp] The CSP part of the filter text. |
| 120 * @param {?string} [rewrite] The rewrite pattern part of the filter text. |
| 121 * |
| 122 * @returns {Array.<string>} An array containing the optimized filter text and |
| 123 * its optimized parts. |
| 124 */ |
| 125 function optimizeRegExpFilterText(text, pattern, domains, sitekeys, csp, |
| 126 rewrite) |
| 127 { |
| 128 if (!sitekeys && !csp && !rewrite && |
| 129 domains && domains.length >= LONG_DOMAINS_THRESHOLD && |
| 130 text.endsWith(domains)) |
| 131 { |
| 132 let copy = strings.get(domains); |
| 133 if (copy) |
| 134 { |
| 135 domains = copy; |
| 136 |
| 137 text = trueSlice(text.substring(0, text.length - domains.length)); |
| 138 |
| 139 if (text[0] == "@" && text[1] == "@") |
| 140 pattern = text.substring(2, 2 + pattern.length); |
| 141 else |
| 142 pattern = text.substring(0, pattern.length); |
| 143 |
| 144 // Note: This must be the last operation on the text in order for this |
| 145 // optimization to work. Any further operations on the text may undo the |
| 146 // optimization. |
| 147 text += domains; |
| 148 } |
| 149 else |
| 150 { |
| 151 strings.set(domains, domains); |
| 152 } |
| 153 } |
| 154 |
| 155 return [text, pattern, domains, sitekeys, csp, rewrite]; |
| 156 } |
| 157 |
| 158 exports.optimizeRegExpFilterText = optimizeRegExpFilterText; |
OLD | NEW |