|
2 | 2 |
|
3 | 3 | const { |
4 | 4 | Array, |
| 5 | + ArrayIsArray, |
5 | 6 | ArrayPrototypeJoin, |
6 | 7 | ArrayPrototypeMap, |
7 | 8 | ArrayPrototypePush, |
@@ -156,52 +157,74 @@ function isURLSearchParams(self) { |
156 | 157 | } |
157 | 158 |
|
158 | 159 | class URLSearchParams { |
| 160 | + [searchParams] = []; |
| 161 | + |
| 162 | + // "associated url object" |
| 163 | + [context] = null; |
| 164 | + |
159 | 165 | // URL Standard says the default value is '', but as undefined and '' have |
160 | 166 | // the same result, undefined is used to prevent unnecessary parsing. |
161 | 167 | // Default parameter is necessary to keep URLSearchParams.length === 0 in |
162 | 168 | // accordance with Web IDL spec. |
163 | 169 | constructor(init = undefined) { |
164 | | - if (init === null || init === undefined) { |
165 | | - this[searchParams] = []; |
| 170 | + if (init == null) { |
| 171 | + // Do nothing |
166 | 172 | } else if (typeof init === 'object' || typeof init === 'function') { |
167 | 173 | const method = init[SymbolIterator]; |
168 | 174 | if (method === this[SymbolIterator]) { |
169 | 175 | // While the spec does not have this branch, we can use it as a |
170 | 176 | // shortcut to avoid having to go through the costly generic iterator. |
171 | 177 | const childParams = init[searchParams]; |
172 | 178 | this[searchParams] = childParams.slice(); |
173 | | - } else if (method !== null && method !== undefined) { |
| 179 | + } else if (method != null) { |
| 180 | + // Sequence<sequence<USVString>> |
174 | 181 | if (typeof method !== 'function') { |
175 | 182 | throw new ERR_ARG_NOT_ITERABLE('Query pairs'); |
176 | 183 | } |
177 | 184 |
|
178 | | - // Sequence<sequence<USVString>> |
179 | | - // Note: per spec we have to first exhaust the lists then process them |
180 | | - const pairs = []; |
| 185 | + // The following implementationd differs from the URL specification: |
| 186 | + // Sequences must first be converted from ECMAScript objects before |
| 187 | + // and operations are done on them, and the operation of converting |
| 188 | + // the sequences would first exhaust the iterators. If the iterator |
| 189 | + // returns something invalid in the middle, whether it would be called |
| 190 | + // after that would be an observable change to the users. |
| 191 | + // Exhausting the iterator and later converting them to USVString comes |
| 192 | + // with a significant cost (~40-80%). In order optimize URLSearchParams |
| 193 | + // creation duration, Node.js merges the iteration and converting |
| 194 | + // iterations into a single iteration. |
181 | 195 | for (const pair of init) { |
182 | | - if ((typeof pair !== 'object' && typeof pair !== 'function') || |
183 | | - pair === null || |
184 | | - typeof pair[SymbolIterator] !== 'function') { |
| 196 | + if (pair == null) { |
185 | 197 | throw new ERR_INVALID_TUPLE('Each query pair', '[name, value]'); |
186 | | - } |
187 | | - const convertedPair = []; |
188 | | - for (const element of pair) |
189 | | - ArrayPrototypePush(convertedPair, toUSVString(element)); |
190 | | - ArrayPrototypePush(pairs, convertedPair); |
191 | | - } |
| 198 | + } else if (ArrayIsArray(pair)) { |
| 199 | + // If innerSequence's size is not 2, then throw a TypeError. |
| 200 | + if (pair.length !== 2) { |
| 201 | + throw new ERR_INVALID_TUPLE('Each query pair', '[name, value]'); |
| 202 | + } |
| 203 | + // Append (innerSequence[0], innerSequence[1]) to querys list. |
| 204 | + ArrayPrototypePush(this[searchParams], toUSVString(pair[0]), toUSVString(pair[1])); |
| 205 | + } else { |
| 206 | + if (((typeof pair !== 'object' && typeof pair !== 'function') || |
| 207 | + typeof pair[SymbolIterator] !== 'function')) { |
| 208 | + throw new ERR_INVALID_TUPLE('Each query pair', '[name, value]'); |
| 209 | + } |
192 | 210 |
|
193 | | - this[searchParams] = []; |
194 | | - for (const pair of pairs) { |
195 | | - if (pair.length !== 2) { |
196 | | - throw new ERR_INVALID_TUPLE('Each query pair', '[name, value]'); |
| 211 | + let length = 0; |
| 212 | + |
| 213 | + for (const element of pair) { |
| 214 | + length++; |
| 215 | + ArrayPrototypePush(this[searchParams], toUSVString(element)); |
| 216 | + } |
| 217 | + |
| 218 | + // If innerSequence's size is not 2, then throw a TypeError. |
| 219 | + if (length !== 2) { |
| 220 | + throw new ERR_INVALID_TUPLE('Each query pair', '[name, value]'); |
| 221 | + } |
197 | 222 | } |
198 | | - ArrayPrototypePush(this[searchParams], pair[0], pair[1]); |
199 | 223 | } |
200 | 224 | } else { |
201 | 225 | // Record<USVString, USVString> |
202 | 226 | // Need to use reflection APIs for full spec compliance. |
203 | 227 | const visited = {}; |
204 | | - this[searchParams] = []; |
205 | 228 | const keys = ReflectOwnKeys(init); |
206 | 229 | for (let i = 0; i < keys.length; i++) { |
207 | 230 | const key = keys[i]; |
@@ -223,13 +246,10 @@ class URLSearchParams { |
223 | 246 | } |
224 | 247 | } |
225 | 248 | } else { |
226 | | - // USVString |
| 249 | + // https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams |
227 | 250 | init = toUSVString(init); |
228 | 251 | this[searchParams] = init ? parseParams(init) : []; |
229 | 252 | } |
230 | | - |
231 | | - // "associated url object" |
232 | | - this[context] = null; |
233 | 253 | } |
234 | 254 |
|
235 | 255 | [inspect.custom](recurseTimes, ctx) { |
|