/src/mozilla-central/toolkit/components/places/SQLFunctions.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : |
2 | | * This Source Code Form is subject to the terms of the Mozilla Public |
3 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
4 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
5 | | |
6 | | #include "mozilla/storage.h" |
7 | | #include "mozilla/dom/URLSearchParams.h" |
8 | | #include "nsString.h" |
9 | | #include "nsUnicharUtils.h" |
10 | | #include "nsWhitespaceTokenizer.h" |
11 | | #include "nsEscape.h" |
12 | | #include "mozIPlacesAutoComplete.h" |
13 | | #include "SQLFunctions.h" |
14 | | #include "nsMathUtils.h" |
15 | | #include "nsUnicodeProperties.h" |
16 | | #include "nsUTF8Utils.h" |
17 | | #include "nsINavHistoryService.h" |
18 | | #include "nsPrintfCString.h" |
19 | | #include "nsNavHistory.h" |
20 | | #include "mozilla/Likely.h" |
21 | | #include "nsVariant.h" |
22 | | |
23 | | // Maximum number of chars to search through. |
24 | | // MatchAutoCompleteFunction won't look for matches over this threshold. |
25 | 0 | #define MAX_CHARS_TO_SEARCH_THROUGH 255 |
26 | | |
27 | | using namespace mozilla::storage; |
28 | | |
29 | | //////////////////////////////////////////////////////////////////////////////// |
30 | | //// Anonymous Helpers |
31 | | |
32 | | namespace { |
33 | | |
34 | | typedef nsACString::const_char_iterator const_char_iterator; |
35 | | typedef nsACString::size_type size_type; |
36 | | typedef nsACString::char_type char_type; |
37 | | |
38 | | /** |
39 | | * Scan forward through UTF-8 text until the next potential character that |
40 | | * could match a given codepoint when lower-cased (false positives are okay). |
41 | | * This avoids having to actually parse the UTF-8 text, which is slow. |
42 | | * |
43 | | * @param aStart |
44 | | * An iterator pointing to the first character position considered. |
45 | | * @param aEnd |
46 | | * An interator pointing to past-the-end of the string. |
47 | | * |
48 | | * @return An iterator pointing to the first potential matching character |
49 | | * within the range [aStart, aEnd). |
50 | | */ |
51 | | static |
52 | | MOZ_ALWAYS_INLINE const_char_iterator |
53 | | nextSearchCandidate(const_char_iterator aStart, |
54 | | const_char_iterator aEnd, |
55 | | uint32_t aSearchFor) |
56 | 0 | { |
57 | 0 | const_char_iterator cur = aStart; |
58 | 0 |
|
59 | 0 | // If the character we search for is ASCII, then we can scan until we find |
60 | 0 | // it or its ASCII uppercase character, modulo the special cases |
61 | 0 | // U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE and U+212A KELVIN SIGN |
62 | 0 | // (which are the only non-ASCII characters that lower-case to ASCII ones). |
63 | 0 | // Since false positives are okay, we approximate ASCII lower-casing by |
64 | 0 | // bit-ORing with 0x20, for increased performance. |
65 | 0 | // |
66 | 0 | // If the character we search for is *not* ASCII, we can ignore everything |
67 | 0 | // that is, since all ASCII characters lower-case to ASCII. |
68 | 0 | // |
69 | 0 | // Because of how UTF-8 uses high-order bits, this will never land us |
70 | 0 | // in the middle of a codepoint. |
71 | 0 | // |
72 | 0 | // The assumptions about Unicode made here are verified in the test_casing |
73 | 0 | // gtest. |
74 | 0 | if (aSearchFor < 128) { |
75 | 0 | // When searching for I or K, we pick out the first byte of the UTF-8 |
76 | 0 | // encoding of the corresponding special case character, and look for it |
77 | 0 | // in the loop below. For other characters we fall back to 0xff, which |
78 | 0 | // is not a valid UTF-8 byte. |
79 | 0 | unsigned char target = (unsigned char)(aSearchFor | 0x20); |
80 | 0 | unsigned char special = 0xff; |
81 | 0 | if (target == 'i' || target == 'k') { |
82 | 0 | special = (target == 'i' ? 0xc4 : 0xe2); |
83 | 0 | } |
84 | 0 |
|
85 | 0 | while (cur < aEnd && (unsigned char)(*cur | 0x20) != target && |
86 | 0 | (unsigned char)*cur != special) { |
87 | 0 | cur++; |
88 | 0 | } |
89 | 0 | } else { |
90 | 0 | const_char_iterator cur = aStart; |
91 | 0 | while (cur < aEnd && (unsigned char)(*cur) < 128) { |
92 | 0 | cur++; |
93 | 0 | } |
94 | 0 | } |
95 | 0 |
|
96 | 0 | return cur; |
97 | 0 | } |
98 | | |
99 | | /** |
100 | | * Check whether a character position is on a word boundary of a UTF-8 string |
101 | | * (rather than within a word). We define "within word" to be any position |
102 | | * between [a-zA-Z] and [a-z] -- this lets us match CamelCase words. |
103 | | * TODO: support non-latin alphabets. |
104 | | * |
105 | | * @param aPos |
106 | | * An iterator pointing to the character position considered. It must |
107 | | * *not* be the first byte of a string. |
108 | | * |
109 | | * @return true if boundary, false otherwise. |
110 | | */ |
111 | | static |
112 | | MOZ_ALWAYS_INLINE bool |
113 | 0 | isOnBoundary(const_char_iterator aPos) { |
114 | 0 | if ('a' <= *aPos && *aPos <= 'z') { |
115 | 0 | char prev = *(aPos - 1) | 0x20; |
116 | 0 | return !('a' <= prev && prev <= 'z'); |
117 | 0 | } |
118 | 0 | return true; |
119 | 0 | } |
120 | | |
121 | | /** |
122 | | * Check whether a token string matches a particular position of a source |
123 | | * string, case insensitively. |
124 | | * |
125 | | * @param aTokenStart |
126 | | * An iterator pointing to the start of the token string. |
127 | | * @param aTokenEnd |
128 | | * An iterator pointing past-the-end of the token string. |
129 | | * @param aSourceStart |
130 | | * An iterator pointing to the position of source string to start |
131 | | * matching at. |
132 | | * @param aSourceEnd |
133 | | * An iterator pointing past-the-end of the source string. |
134 | | * |
135 | | * @return true if the string [aTokenStart, aTokenEnd) matches the start of |
136 | | * the string [aSourceStart, aSourceEnd, false otherwise. |
137 | | */ |
138 | | static |
139 | | MOZ_ALWAYS_INLINE bool |
140 | | stringMatch(const_char_iterator aTokenStart, |
141 | | const_char_iterator aTokenEnd, |
142 | | const_char_iterator aSourceStart, |
143 | | const_char_iterator aSourceEnd) |
144 | 0 | { |
145 | 0 | const_char_iterator tokenCur = aTokenStart, sourceCur = aSourceStart; |
146 | 0 |
|
147 | 0 | while (tokenCur < aTokenEnd) { |
148 | 0 | if (sourceCur >= aSourceEnd) { |
149 | 0 | return false; |
150 | 0 | } |
151 | 0 | |
152 | 0 | bool error; |
153 | 0 | if (!CaseInsensitiveUTF8CharsEqual(sourceCur, tokenCur, |
154 | 0 | aSourceEnd, aTokenEnd, |
155 | 0 | &sourceCur, &tokenCur, &error)) { |
156 | 0 | return false; |
157 | 0 | } |
158 | 0 | } |
159 | 0 |
|
160 | 0 | return true; |
161 | 0 | } |
162 | | |
163 | | enum FindInStringBehavior { |
164 | | eFindOnBoundary, |
165 | | eFindAnywhere |
166 | | }; |
167 | | |
168 | | /** |
169 | | * Common implementation for findAnywhere and findOnBoundary. |
170 | | * |
171 | | * @param aToken |
172 | | * The token we're searching for |
173 | | * @param aSourceString |
174 | | * The string in which we're searching |
175 | | * @param aBehavior |
176 | | * eFindOnBoundary if we should only consider matchines which occur on |
177 | | * word boundaries, or eFindAnywhere if we should consider matches |
178 | | * which appear anywhere. |
179 | | * |
180 | | * @return true if aToken was found in aSourceString, false otherwise. |
181 | | */ |
182 | | static |
183 | | bool |
184 | | findInString(const nsDependentCSubstring &aToken, |
185 | | const nsACString &aSourceString, |
186 | | FindInStringBehavior aBehavior) |
187 | 0 | { |
188 | 0 | // GetLowerUTF8Codepoint assumes that there's at least one byte in |
189 | 0 | // the string, so don't pass an empty token here. |
190 | 0 | MOZ_ASSERT(!aToken.IsEmpty(), "Don't search for an empty token!"); |
191 | 0 |
|
192 | 0 | // We cannot match anything if there is nothing to search. |
193 | 0 | if (aSourceString.IsEmpty()) { |
194 | 0 | return false; |
195 | 0 | } |
196 | 0 | |
197 | 0 | const_char_iterator tokenStart(aToken.BeginReading()), |
198 | 0 | tokenEnd(aToken.EndReading()), |
199 | 0 | tokenNext, |
200 | 0 | sourceStart(aSourceString.BeginReading()), |
201 | 0 | sourceEnd(aSourceString.EndReading()), |
202 | 0 | sourceCur(sourceStart), |
203 | 0 | sourceNext; |
204 | 0 |
|
205 | 0 | uint32_t tokenFirstChar = |
206 | 0 | GetLowerUTF8Codepoint(tokenStart, tokenEnd, &tokenNext); |
207 | 0 | if (tokenFirstChar == uint32_t(-1)) { |
208 | 0 | return false; |
209 | 0 | } |
210 | 0 | |
211 | 0 | for (;;) { |
212 | 0 | // Scan forward to the next viable candidate (if any). |
213 | 0 | sourceCur = nextSearchCandidate(sourceCur, sourceEnd, tokenFirstChar); |
214 | 0 | if (sourceCur == sourceEnd) { |
215 | 0 | break; |
216 | 0 | } |
217 | 0 | |
218 | 0 | // Check whether the first character in the token matches the character |
219 | 0 | // at sourceCur. At the same time, get a pointer to the next character |
220 | 0 | // in the source. |
221 | 0 | uint32_t sourceFirstChar = |
222 | 0 | GetLowerUTF8Codepoint(sourceCur, sourceEnd, &sourceNext); |
223 | 0 | if (sourceFirstChar == uint32_t(-1)) { |
224 | 0 | return false; |
225 | 0 | } |
226 | 0 | |
227 | 0 | if (sourceFirstChar == tokenFirstChar && |
228 | 0 | (aBehavior != eFindOnBoundary || sourceCur == sourceStart || |
229 | 0 | isOnBoundary(sourceCur)) && |
230 | 0 | stringMatch(tokenNext, tokenEnd, sourceNext, sourceEnd)) |
231 | 0 | { |
232 | 0 | return true; |
233 | 0 | } |
234 | 0 | |
235 | 0 | sourceCur = sourceNext; |
236 | 0 | } |
237 | 0 |
|
238 | 0 | return false; |
239 | 0 | } |
240 | | |
241 | | static |
242 | | MOZ_ALWAYS_INLINE nsDependentCString |
243 | 0 | getSharedUTF8String(mozIStorageValueArray* aValues, uint32_t aIndex) { |
244 | 0 | uint32_t len; |
245 | 0 | const char* str = aValues->AsSharedUTF8String(aIndex, &len); |
246 | 0 | if (!str) { |
247 | 0 | return nsDependentCString("", (uint32_t)0); |
248 | 0 | } |
249 | 0 | return nsDependentCString(str, len); |
250 | 0 | } |
251 | | |
252 | | class MOZ_STACK_CLASS GetQueryParamIterator final : |
253 | | public URLParams::ForEachIterator |
254 | | { |
255 | | public: |
256 | | explicit GetQueryParamIterator(const nsCString& aParamName, |
257 | | nsVariant* aResult) |
258 | | : mParamName(aParamName) |
259 | | , mResult(aResult) |
260 | 0 | {} |
261 | | |
262 | | bool URLParamsIterator(const nsAString& aName, |
263 | | const nsAString& aValue) override |
264 | 0 | { |
265 | 0 | NS_ConvertUTF16toUTF8 name(aName); |
266 | 0 | if (!mParamName.Equals(name)) { |
267 | 0 | return true; |
268 | 0 | } |
269 | 0 | mResult->SetAsAString(aValue); |
270 | 0 | return false; |
271 | 0 | } |
272 | | private: |
273 | | const nsCString& mParamName; |
274 | | nsVariant* mResult; |
275 | | }; |
276 | | |
277 | | /** |
278 | | * Gets the length of the prefix in a URI spec. "Prefix" is defined to be the |
279 | | * scheme, colon, and, if present, two slashes. |
280 | | * |
281 | | * Examples: |
282 | | * |
283 | | * http://example.com |
284 | | * ~~~~~~~ |
285 | | * => length == 7 |
286 | | * |
287 | | * foo:example |
288 | | * ~~~~ |
289 | | * => length == 4 |
290 | | * |
291 | | * not a spec |
292 | | * => length == 0 |
293 | | * |
294 | | * @param aSpec |
295 | | * A URI spec, or a string that may be a URI spec. |
296 | | * @return The length of the prefix in the spec. If there isn't a prefix, |
297 | | * returns 0. |
298 | | */ |
299 | | static |
300 | | MOZ_ALWAYS_INLINE size_type |
301 | | getPrefixLength(const nsACString &aSpec) |
302 | 0 | { |
303 | 0 | // To keep the search bounded, look at 64 characters at most. The longest |
304 | 0 | // IANA schemes are ~30, so double that and round up to a nice number. |
305 | 0 | size_type length = std::min(static_cast<size_type>(64), aSpec.Length()); |
306 | 0 | for (size_type i = 0; i < length; ++i) { |
307 | 0 | if (aSpec[i] == static_cast<char_type>(':')) { |
308 | 0 | // Found the ':'. Now skip past "//", if present. |
309 | 0 | if (i + 2 < aSpec.Length() && |
310 | 0 | aSpec[i + 1] == static_cast<char_type>('/') && |
311 | 0 | aSpec[i + 2] == static_cast<char_type>('/')) { |
312 | 0 | i += 2; |
313 | 0 | } |
314 | 0 | return i + 1; |
315 | 0 | } |
316 | 0 | } |
317 | 0 | return 0; |
318 | 0 | } |
319 | | |
320 | | /** |
321 | | * Gets the index in a URI spec of the host and port substring and optionally |
322 | | * its length. |
323 | | * |
324 | | * Examples: |
325 | | * |
326 | | * http://example.com/ |
327 | | * ~~~~~~~~~~~ |
328 | | * => index == 7, length == 11 |
329 | | * |
330 | | * http://example.com:8888/ |
331 | | * ~~~~~~~~~~~~~~~~ |
332 | | * => index == 7, length == 16 |
333 | | * |
334 | | * http://user:pass@example.com/ |
335 | | * ~~~~~~~~~~~ |
336 | | * => index == 17, length == 11 |
337 | | * |
338 | | * foo:example |
339 | | * ~~~~~~~ |
340 | | * => index == 4, length == 7 |
341 | | * |
342 | | * not a spec |
343 | | * ~~~~~~~~~~ |
344 | | * => index == 0, length == 10 |
345 | | * |
346 | | * @param aSpec |
347 | | * A URI spec, or a string that may be a URI spec. |
348 | | * @param _hostAndPortLength |
349 | | * The length of the host and port substring is returned through this |
350 | | * param. Pass null if you don't care. |
351 | | * @return The length of the host and port substring in the spec. If aSpec |
352 | | * doesn't look like a URI, then the entire aSpec is assumed to be a |
353 | | * "host and port", and this returns 0, and _hostAndPortLength will be |
354 | | * the length of aSpec. |
355 | | */ |
356 | | static |
357 | | MOZ_ALWAYS_INLINE size_type |
358 | | indexOfHostAndPort(const nsACString &aSpec, |
359 | | size_type *_hostAndPortLength) |
360 | 0 | { |
361 | 0 | size_type index = getPrefixLength(aSpec); |
362 | 0 | size_type i = index; |
363 | 0 | for (; i < aSpec.Length(); ++i) { |
364 | 0 | // RFC 3986 (URIs): The origin ("authority") is terminated by '/', '?', or |
365 | 0 | // '#' (or the end of the URI). |
366 | 0 | if (aSpec[i] == static_cast<char_type>('/') || |
367 | 0 | aSpec[i] == static_cast<char_type>('?') || |
368 | 0 | aSpec[i] == static_cast<char_type>('#')) { |
369 | 0 | break; |
370 | 0 | } |
371 | 0 | // RFC 3986: '@' marks the end of the userinfo component. |
372 | 0 | if (aSpec[i] == static_cast<char_type>('@')) { |
373 | 0 | index = i + 1; |
374 | 0 | } |
375 | 0 | } |
376 | 0 | if (_hostAndPortLength) { |
377 | 0 | *_hostAndPortLength = i - index; |
378 | 0 | } |
379 | 0 | return index; |
380 | 0 | } |
381 | | |
382 | | } // End anonymous namespace |
383 | | |
384 | | namespace mozilla { |
385 | | namespace places { |
386 | | |
387 | | //////////////////////////////////////////////////////////////////////////////// |
388 | | //// AutoComplete Matching Function |
389 | | |
390 | | /* static */ |
391 | | nsresult |
392 | | MatchAutoCompleteFunction::create(mozIStorageConnection *aDBConn) |
393 | 0 | { |
394 | 0 | RefPtr<MatchAutoCompleteFunction> function = |
395 | 0 | new MatchAutoCompleteFunction(); |
396 | 0 |
|
397 | 0 | nsresult rv = aDBConn->CreateFunction( |
398 | 0 | NS_LITERAL_CSTRING("autocomplete_match"), kArgIndexLength, function |
399 | 0 | ); |
400 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
401 | 0 |
|
402 | 0 | return NS_OK; |
403 | 0 | } |
404 | | |
405 | | /* static */ |
406 | | nsDependentCSubstring |
407 | | MatchAutoCompleteFunction::fixupURISpec(const nsACString &aURISpec, |
408 | | int32_t aMatchBehavior, |
409 | | nsACString &aSpecBuf) |
410 | 0 | { |
411 | 0 | nsDependentCSubstring fixedSpec; |
412 | 0 |
|
413 | 0 | // Try to unescape the string. If that succeeds and yields a different |
414 | 0 | // string which is also valid UTF-8, we'll use it. |
415 | 0 | // Otherwise, we will simply use our original string. |
416 | 0 | bool unescaped = NS_UnescapeURL(aURISpec.BeginReading(), |
417 | 0 | aURISpec.Length(), esc_SkipControl, aSpecBuf); |
418 | 0 | if (unescaped && IsUTF8(aSpecBuf)) { |
419 | 0 | fixedSpec.Rebind(aSpecBuf, 0); |
420 | 0 | } else { |
421 | 0 | fixedSpec.Rebind(aURISpec, 0); |
422 | 0 | } |
423 | 0 |
|
424 | 0 | if (aMatchBehavior == mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED) |
425 | 0 | return fixedSpec; |
426 | 0 | |
427 | 0 | if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("http://"))) { |
428 | 0 | fixedSpec.Rebind(fixedSpec, 7); |
429 | 0 | } else if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("https://"))) { |
430 | 0 | fixedSpec.Rebind(fixedSpec, 8); |
431 | 0 | } else if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("ftp://"))) { |
432 | 0 | fixedSpec.Rebind(fixedSpec, 6); |
433 | 0 | } |
434 | 0 |
|
435 | 0 | return fixedSpec; |
436 | 0 | } |
437 | | |
438 | | /* static */ |
439 | | bool |
440 | | MatchAutoCompleteFunction::findAnywhere(const nsDependentCSubstring &aToken, |
441 | | const nsACString &aSourceString) |
442 | 0 | { |
443 | 0 | // We can't use FindInReadable here; it works only for ASCII. |
444 | 0 |
|
445 | 0 | return findInString(aToken, aSourceString, eFindAnywhere); |
446 | 0 | } |
447 | | |
448 | | /* static */ |
449 | | bool |
450 | | MatchAutoCompleteFunction::findOnBoundary(const nsDependentCSubstring &aToken, |
451 | | const nsACString &aSourceString) |
452 | 0 | { |
453 | 0 | return findInString(aToken, aSourceString, eFindOnBoundary); |
454 | 0 | } |
455 | | |
456 | | /* static */ |
457 | | bool |
458 | | MatchAutoCompleteFunction::findBeginning(const nsDependentCSubstring &aToken, |
459 | | const nsACString &aSourceString) |
460 | 0 | { |
461 | 0 | MOZ_ASSERT(!aToken.IsEmpty(), "Don't search for an empty token!"); |
462 | 0 |
|
463 | 0 | // We can't use StringBeginsWith here, unfortunately. Although it will |
464 | 0 | // happily take a case-insensitive UTF8 comparator, it eventually calls |
465 | 0 | // nsACString::Equals, which checks that the two strings contain the same |
466 | 0 | // number of bytes before calling the comparator. Two characters may be |
467 | 0 | // case-insensitively equal while taking up different numbers of bytes, so |
468 | 0 | // this is not what we want. |
469 | 0 |
|
470 | 0 | const_char_iterator tokenStart(aToken.BeginReading()), |
471 | 0 | tokenEnd(aToken.EndReading()), |
472 | 0 | sourceStart(aSourceString.BeginReading()), |
473 | 0 | sourceEnd(aSourceString.EndReading()); |
474 | 0 |
|
475 | 0 | bool dummy; |
476 | 0 | while (sourceStart < sourceEnd && |
477 | 0 | CaseInsensitiveUTF8CharsEqual(sourceStart, tokenStart, |
478 | 0 | sourceEnd, tokenEnd, |
479 | 0 | &sourceStart, &tokenStart, &dummy)) { |
480 | 0 |
|
481 | 0 | // We found the token! |
482 | 0 | if (tokenStart >= tokenEnd) { |
483 | 0 | return true; |
484 | 0 | } |
485 | 0 | } |
486 | 0 |
|
487 | 0 | // We don't need to check CaseInsensitiveUTF8CharsEqual's error condition |
488 | 0 | // (stored in |dummy|), since the function will return false if it |
489 | 0 | // encounters an error. |
490 | 0 |
|
491 | 0 | return false; |
492 | 0 | } |
493 | | |
494 | | /* static */ |
495 | | bool |
496 | | MatchAutoCompleteFunction::findBeginningCaseSensitive( |
497 | | const nsDependentCSubstring &aToken, |
498 | | const nsACString &aSourceString) |
499 | 0 | { |
500 | 0 | MOZ_ASSERT(!aToken.IsEmpty(), "Don't search for an empty token!"); |
501 | 0 |
|
502 | 0 | return StringBeginsWith(aSourceString, aToken); |
503 | 0 | } |
504 | | |
505 | | /* static */ |
506 | | MatchAutoCompleteFunction::searchFunctionPtr |
507 | | MatchAutoCompleteFunction::getSearchFunction(int32_t aBehavior) |
508 | 0 | { |
509 | 0 | switch (aBehavior) { |
510 | 0 | case mozIPlacesAutoComplete::MATCH_ANYWHERE: |
511 | 0 | case mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED: |
512 | 0 | return findAnywhere; |
513 | 0 | case mozIPlacesAutoComplete::MATCH_BEGINNING: |
514 | 0 | return findBeginning; |
515 | 0 | case mozIPlacesAutoComplete::MATCH_BEGINNING_CASE_SENSITIVE: |
516 | 0 | return findBeginningCaseSensitive; |
517 | 0 | case mozIPlacesAutoComplete::MATCH_BOUNDARY: |
518 | 0 | default: |
519 | 0 | return findOnBoundary; |
520 | 0 | }; |
521 | 0 | } |
522 | | |
523 | | NS_IMPL_ISUPPORTS( |
524 | | MatchAutoCompleteFunction, |
525 | | mozIStorageFunction |
526 | | ) |
527 | | |
528 | | MatchAutoCompleteFunction::MatchAutoCompleteFunction() |
529 | | : mCachedZero(new IntegerVariant(0)) |
530 | | , mCachedOne(new IntegerVariant(1)) |
531 | 0 | { |
532 | 0 | static_assert(IntegerVariant::HasThreadSafeRefCnt::value, |
533 | 0 | "Caching assumes that variants have thread-safe refcounting"); |
534 | 0 | } |
535 | | |
536 | | NS_IMETHODIMP |
537 | | MatchAutoCompleteFunction::OnFunctionCall(mozIStorageValueArray *aArguments, |
538 | | nsIVariant **_result) |
539 | 0 | { |
540 | 0 | // Macro to make the code a bit cleaner and easier to read. Operates on |
541 | 0 | // searchBehavior. |
542 | 0 | int32_t searchBehavior = aArguments->AsInt32(kArgIndexSearchBehavior); |
543 | 0 | #define HAS_BEHAVIOR(aBitName) \ |
544 | 0 | (searchBehavior & mozIPlacesAutoComplete::BEHAVIOR_##aBitName) |
545 | 0 |
|
546 | 0 | nsDependentCString searchString = |
547 | 0 | getSharedUTF8String(aArguments, kArgSearchString); |
548 | 0 | nsDependentCString url = |
549 | 0 | getSharedUTF8String(aArguments, kArgIndexURL); |
550 | 0 |
|
551 | 0 | int32_t matchBehavior = aArguments->AsInt32(kArgIndexMatchBehavior); |
552 | 0 |
|
553 | 0 | // We only want to filter javascript: URLs if we are not supposed to search |
554 | 0 | // for them, and the search does not start with "javascript:". |
555 | 0 | if (matchBehavior != mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED && |
556 | 0 | StringBeginsWith(url, NS_LITERAL_CSTRING("javascript:")) && |
557 | 0 | !HAS_BEHAVIOR(JAVASCRIPT) && |
558 | 0 | !StringBeginsWith(searchString, NS_LITERAL_CSTRING("javascript:"))) { |
559 | 0 | NS_ADDREF(*_result = mCachedZero); |
560 | 0 | return NS_OK; |
561 | 0 | } |
562 | 0 |
|
563 | 0 | int32_t visitCount = aArguments->AsInt32(kArgIndexVisitCount); |
564 | 0 | bool typed = aArguments->AsInt32(kArgIndexTyped) ? true : false; |
565 | 0 | bool bookmark = aArguments->AsInt32(kArgIndexBookmark) ? true : false; |
566 | 0 | nsDependentCString tags = getSharedUTF8String(aArguments, kArgIndexTags); |
567 | 0 | int32_t openPageCount = aArguments->AsInt32(kArgIndexOpenPageCount); |
568 | 0 | bool matches = false; |
569 | 0 | if (HAS_BEHAVIOR(RESTRICT)) { |
570 | 0 | // Make sure we match all the filter requirements. If a given restriction |
571 | 0 | // is active, make sure the corresponding condition is not true. |
572 | 0 | matches = (!HAS_BEHAVIOR(HISTORY) || visitCount > 0) && |
573 | 0 | (!HAS_BEHAVIOR(TYPED) || typed) && |
574 | 0 | (!HAS_BEHAVIOR(BOOKMARK) || bookmark) && |
575 | 0 | (!HAS_BEHAVIOR(TAG) || !tags.IsVoid()) && |
576 | 0 | (!HAS_BEHAVIOR(OPENPAGE) || openPageCount > 0); |
577 | 0 | } else { |
578 | 0 | // Make sure that we match all the filter requirements and that the |
579 | 0 | // corresponding condition is true if at least a given restriction is active. |
580 | 0 | matches = (HAS_BEHAVIOR(HISTORY) && visitCount > 0) || |
581 | 0 | (HAS_BEHAVIOR(TYPED) && typed) || |
582 | 0 | (HAS_BEHAVIOR(BOOKMARK) && bookmark) || |
583 | 0 | (HAS_BEHAVIOR(TAG) && !tags.IsVoid()) || |
584 | 0 | (HAS_BEHAVIOR(OPENPAGE) && openPageCount > 0); |
585 | 0 | } |
586 | 0 |
|
587 | 0 | if (!matches) { |
588 | 0 | NS_ADDREF(*_result = mCachedZero); |
589 | 0 | return NS_OK; |
590 | 0 | } |
591 | 0 |
|
592 | 0 | // Obtain our search function. |
593 | 0 | searchFunctionPtr searchFunction = getSearchFunction(matchBehavior); |
594 | 0 |
|
595 | 0 | // Clean up our URI spec and prepare it for searching. |
596 | 0 | nsCString fixedUrlBuf; |
597 | 0 | nsDependentCSubstring fixedUrl = |
598 | 0 | fixupURISpec(url, matchBehavior, fixedUrlBuf); |
599 | 0 | // Limit the number of chars we search through. |
600 | 0 | const nsDependentCSubstring& trimmedUrl = |
601 | 0 | Substring(fixedUrl, 0, MAX_CHARS_TO_SEARCH_THROUGH); |
602 | 0 |
|
603 | 0 | nsDependentCString title = getSharedUTF8String(aArguments, kArgIndexTitle); |
604 | 0 | // Limit the number of chars we search through. |
605 | 0 | const nsDependentCSubstring& trimmedTitle = |
606 | 0 | Substring(title, 0, MAX_CHARS_TO_SEARCH_THROUGH); |
607 | 0 |
|
608 | 0 | // Determine if every token matches either the bookmark title, tags, page |
609 | 0 | // title, or page URL. |
610 | 0 | nsCWhitespaceTokenizer tokenizer(searchString); |
611 | 0 | while (matches && tokenizer.hasMoreTokens()) { |
612 | 0 | const nsDependentCSubstring &token = tokenizer.nextToken(); |
613 | 0 |
|
614 | 0 | if (HAS_BEHAVIOR(TITLE) && HAS_BEHAVIOR(URL)) { |
615 | 0 | matches = (searchFunction(token, trimmedTitle) || |
616 | 0 | searchFunction(token, tags)) && |
617 | 0 | searchFunction(token, trimmedUrl); |
618 | 0 | } |
619 | 0 | else if (HAS_BEHAVIOR(TITLE)) { |
620 | 0 | matches = searchFunction(token, trimmedTitle) || |
621 | 0 | searchFunction(token, tags); |
622 | 0 | } |
623 | 0 | else if (HAS_BEHAVIOR(URL)) { |
624 | 0 | matches = searchFunction(token, trimmedUrl); |
625 | 0 | } |
626 | 0 | else { |
627 | 0 | matches = searchFunction(token, trimmedTitle) || |
628 | 0 | searchFunction(token, tags) || |
629 | 0 | searchFunction(token, trimmedUrl); |
630 | 0 | } |
631 | 0 | } |
632 | 0 |
|
633 | 0 | NS_ADDREF(*_result = (matches ? mCachedOne : mCachedZero)); |
634 | 0 | return NS_OK; |
635 | 0 | #undef HAS_BEHAVIOR |
636 | 0 | } |
637 | | |
638 | | |
639 | | //////////////////////////////////////////////////////////////////////////////// |
640 | | //// Frecency Calculation Function |
641 | | |
642 | | /* static */ |
643 | | nsresult |
644 | | CalculateFrecencyFunction::create(mozIStorageConnection *aDBConn) |
645 | 0 | { |
646 | 0 | RefPtr<CalculateFrecencyFunction> function = |
647 | 0 | new CalculateFrecencyFunction(); |
648 | 0 |
|
649 | 0 | nsresult rv = aDBConn->CreateFunction( |
650 | 0 | NS_LITERAL_CSTRING("calculate_frecency"), -1, function |
651 | 0 | ); |
652 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
653 | 0 |
|
654 | 0 | return NS_OK; |
655 | 0 | } |
656 | | |
657 | | NS_IMPL_ISUPPORTS( |
658 | | CalculateFrecencyFunction, |
659 | | mozIStorageFunction |
660 | | ) |
661 | | |
662 | | NS_IMETHODIMP |
663 | | CalculateFrecencyFunction::OnFunctionCall(mozIStorageValueArray *aArguments, |
664 | | nsIVariant **_result) |
665 | 0 | { |
666 | 0 | // Fetch arguments. Use default values if they were omitted. |
667 | 0 | uint32_t numEntries; |
668 | 0 | nsresult rv = aArguments->GetNumEntries(&numEntries); |
669 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
670 | 0 | MOZ_ASSERT(numEntries <= 2, "unexpected number of arguments"); |
671 | 0 |
|
672 | 0 | int64_t pageId = aArguments->AsInt64(0); |
673 | 0 | MOZ_ASSERT(pageId > 0, "Should always pass a valid page id"); |
674 | 0 | if (pageId <= 0) { |
675 | 0 | NS_ADDREF(*_result = new IntegerVariant(0)); |
676 | 0 | return NS_OK; |
677 | 0 | } |
678 | 0 |
|
679 | 0 | enum RedirectBonus { |
680 | 0 | eUnknown, |
681 | 0 | eRedirect, |
682 | 0 | eNormal |
683 | 0 | }; |
684 | 0 |
|
685 | 0 | RedirectBonus mostRecentVisitBonus = eUnknown; |
686 | 0 |
|
687 | 0 | if (numEntries > 1) { |
688 | 0 | mostRecentVisitBonus = aArguments->AsInt32(1) ? eRedirect : eNormal; |
689 | 0 | } |
690 | 0 |
|
691 | 0 | int32_t typed = 0; |
692 | 0 | int32_t visitCount = 0; |
693 | 0 | bool hasBookmark = false; |
694 | 0 | int32_t isQuery = 0; |
695 | 0 | float pointsForSampledVisits = 0.0; |
696 | 0 | int32_t numSampledVisits = 0; |
697 | 0 | int32_t bonus = 0; |
698 | 0 |
|
699 | 0 | // This is a const version of the history object for thread-safety. |
700 | 0 | const nsNavHistory* history = nsNavHistory::GetConstHistoryService(); |
701 | 0 | NS_ENSURE_STATE(history); |
702 | 0 | RefPtr<Database> DB = Database::GetDatabase(); |
703 | 0 | NS_ENSURE_STATE(DB); |
704 | 0 |
|
705 | 0 |
|
706 | 0 | // Fetch the page stats from the database. |
707 | 0 | { |
708 | 0 | nsCOMPtr<mozIStorageStatement> getPageInfo = DB->GetStatement( |
709 | 0 | "SELECT typed, visit_count, foreign_count, " |
710 | 0 | "(substr(url, 0, 7) = 'place:') " |
711 | 0 | "FROM moz_places " |
712 | 0 | "WHERE id = :page_id " |
713 | 0 | ); |
714 | 0 | NS_ENSURE_STATE(getPageInfo); |
715 | 0 | mozStorageStatementScoper infoScoper(getPageInfo); |
716 | 0 |
|
717 | 0 | rv = getPageInfo->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), pageId); |
718 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
719 | 0 |
|
720 | 0 | bool hasResult = false; |
721 | 0 | rv = getPageInfo->ExecuteStep(&hasResult); |
722 | 0 | NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_UNEXPECTED); |
723 | 0 |
|
724 | 0 | rv = getPageInfo->GetInt32(0, &typed); |
725 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
726 | 0 | rv = getPageInfo->GetInt32(1, &visitCount); |
727 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
728 | 0 | int32_t foreignCount = 0; |
729 | 0 | rv = getPageInfo->GetInt32(2, &foreignCount); |
730 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
731 | 0 | hasBookmark = foreignCount > 0; |
732 | 0 | rv = getPageInfo->GetInt32(3, &isQuery); |
733 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
734 | 0 | } |
735 | 0 |
|
736 | 0 | if (visitCount > 0) { |
737 | 0 | // Get a sample of the last visits to the page, to calculate its weight. |
738 | 0 | // In case the visit is a redirect target, calculate the frecency |
739 | 0 | // as if the original page was visited. |
740 | 0 | // If it's a redirect source, we may want to use a lower bonus. |
741 | 0 | nsCString redirectsTransitionFragment = |
742 | 0 | nsPrintfCString("%d AND %d ", nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT, |
743 | 0 | nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY); |
744 | 0 | nsCOMPtr<mozIStorageStatement> getVisits = DB->GetStatement( |
745 | 0 | NS_LITERAL_CSTRING( |
746 | 0 | "/* do not warn (bug 659740 - SQLite may ignore index if few visits exist) */" |
747 | 0 | "SELECT " |
748 | 0 | "IFNULL(origin.visit_type, v.visit_type) AS visit_type, " |
749 | 0 | "target.visit_type AS target_visit_type, " |
750 | 0 | "ROUND((strftime('%s','now','localtime','utc') - v.visit_date/1000000)/86400) AS age_in_days " |
751 | 0 | "FROM moz_historyvisits v " |
752 | 0 | "LEFT JOIN moz_historyvisits origin ON origin.id = v.from_visit " |
753 | 0 | "AND v.visit_type BETWEEN " |
754 | 0 | ) + redirectsTransitionFragment + NS_LITERAL_CSTRING( |
755 | 0 | "LEFT JOIN moz_historyvisits target ON v.id = target.from_visit " |
756 | 0 | "AND target.visit_type BETWEEN " |
757 | 0 | ) + redirectsTransitionFragment + NS_LITERAL_CSTRING( |
758 | 0 | "WHERE v.place_id = :page_id " |
759 | 0 | "ORDER BY v.visit_date DESC " |
760 | 0 | "LIMIT :max_visits " |
761 | 0 | ) |
762 | 0 | ); |
763 | 0 | NS_ENSURE_STATE(getVisits); |
764 | 0 | mozStorageStatementScoper visitsScoper(getVisits); |
765 | 0 | rv = getVisits->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), pageId); |
766 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
767 | 0 | rv = getVisits->BindInt32ByName(NS_LITERAL_CSTRING("max_visits"), |
768 | 0 | history->GetNumVisitsForFrecency()); |
769 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
770 | 0 |
|
771 | 0 | // Fetch only a limited number of recent visits. |
772 | 0 | bool hasResult = false; |
773 | 0 | while (NS_SUCCEEDED(getVisits->ExecuteStep(&hasResult)) && hasResult) { |
774 | 0 | // If this is a redirect target, we'll use the visitType of the source, |
775 | 0 | // otherwise the actual visitType. |
776 | 0 | int32_t visitType = getVisits->AsInt32(0); |
777 | 0 |
|
778 | 0 | // When adding a new visit, we should haved passed-in whether we should |
779 | 0 | // use the redirect bonus. We can't fetch this information from the |
780 | 0 | // database, because we only store redirect targets. |
781 | 0 | // For older visits we extract the value from the database. |
782 | 0 | bool useRedirectBonus = mostRecentVisitBonus == eRedirect; |
783 | 0 | if (mostRecentVisitBonus == eUnknown || numSampledVisits > 0) { |
784 | 0 | int32_t targetVisitType = getVisits->AsInt32(1); |
785 | 0 | useRedirectBonus = targetVisitType == nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT || |
786 | 0 | (targetVisitType == nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY && |
787 | 0 | visitType != nsINavHistoryService::TRANSITION_TYPED); |
788 | 0 | } |
789 | 0 |
|
790 | 0 | bonus = history->GetFrecencyTransitionBonus(visitType, true, useRedirectBonus); |
791 | 0 |
|
792 | 0 | // Add the bookmark visit bonus. |
793 | 0 | if (hasBookmark) { |
794 | 0 | bonus += history->GetFrecencyTransitionBonus(nsINavHistoryService::TRANSITION_BOOKMARK, true); |
795 | 0 | } |
796 | 0 |
|
797 | 0 | // If bonus was zero, we can skip the work to determine the weight. |
798 | 0 | if (bonus) { |
799 | 0 | int32_t ageInDays = getVisits->AsInt32(2); |
800 | 0 | int32_t weight = history->GetFrecencyAgedWeight(ageInDays); |
801 | 0 | pointsForSampledVisits += (float)(weight * (bonus / 100.0)); |
802 | 0 | } |
803 | 0 |
|
804 | 0 | numSampledVisits++; |
805 | 0 | } |
806 | 0 | } |
807 | 0 |
|
808 | 0 | // If we sampled some visits for this page, use the calculated weight. |
809 | 0 | if (numSampledVisits) { |
810 | 0 | // We were unable to calculate points, maybe cause all the visits in the |
811 | 0 | // sample had a zero bonus. Though, we know the page has some past valid |
812 | 0 | // visit, or visit_count would be zero. Thus we set the frecency to |
813 | 0 | // -1, so they are still shown in autocomplete. |
814 | 0 | if (!pointsForSampledVisits) { |
815 | 0 | NS_ADDREF(*_result = new IntegerVariant(-1)); |
816 | 0 | } |
817 | 0 | else { |
818 | 0 | // Estimate frecency using the sampled visits. |
819 | 0 | // Use ceilf() so that we don't round down to 0, which |
820 | 0 | // would cause us to completely ignore the place during autocomplete. |
821 | 0 | NS_ADDREF(*_result = new IntegerVariant((int32_t) ceilf(visitCount * ceilf(pointsForSampledVisits) / numSampledVisits))); |
822 | 0 | } |
823 | 0 | return NS_OK; |
824 | 0 | } |
825 | 0 |
|
826 | 0 | // Otherwise this page has no visits, it may be bookmarked. |
827 | 0 | if (!hasBookmark || isQuery) { |
828 | 0 | NS_ADDREF(*_result = new IntegerVariant(0)); |
829 | 0 | return NS_OK; |
830 | 0 | } |
831 | 0 |
|
832 | 0 | // For unvisited bookmarks, produce a non-zero frecency, so that they show |
833 | 0 | // up in URL bar autocomplete. |
834 | 0 | visitCount = 1; |
835 | 0 |
|
836 | 0 | // Make it so something bookmarked and typed will have a higher frecency |
837 | 0 | // than something just typed or just bookmarked. |
838 | 0 | bonus += history->GetFrecencyTransitionBonus(nsINavHistoryService::TRANSITION_BOOKMARK, false); |
839 | 0 | if (typed) { |
840 | 0 | bonus += history->GetFrecencyTransitionBonus(nsINavHistoryService::TRANSITION_TYPED, false); |
841 | 0 | } |
842 | 0 |
|
843 | 0 | // Assume "now" as our ageInDays, so use the first bucket. |
844 | 0 | pointsForSampledVisits = history->GetFrecencyBucketWeight(1) * (bonus / (float)100.0); |
845 | 0 |
|
846 | 0 | // use ceilf() so that we don't round down to 0, which |
847 | 0 | // would cause us to completely ignore the place during autocomplete |
848 | 0 | NS_ADDREF(*_result = new IntegerVariant((int32_t) ceilf(visitCount * ceilf(pointsForSampledVisits)))); |
849 | 0 |
|
850 | 0 | return NS_OK; |
851 | 0 | } |
852 | | |
853 | | //////////////////////////////////////////////////////////////////////////////// |
854 | | //// GUID Creation Function |
855 | | |
856 | | /* static */ |
857 | | nsresult |
858 | | GenerateGUIDFunction::create(mozIStorageConnection *aDBConn) |
859 | 0 | { |
860 | 0 | RefPtr<GenerateGUIDFunction> function = new GenerateGUIDFunction(); |
861 | 0 | nsresult rv = aDBConn->CreateFunction( |
862 | 0 | NS_LITERAL_CSTRING("generate_guid"), 0, function |
863 | 0 | ); |
864 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
865 | 0 |
|
866 | 0 | return NS_OK; |
867 | 0 | } |
868 | | |
869 | | NS_IMPL_ISUPPORTS( |
870 | | GenerateGUIDFunction, |
871 | | mozIStorageFunction |
872 | | ) |
873 | | |
874 | | NS_IMETHODIMP |
875 | | GenerateGUIDFunction::OnFunctionCall(mozIStorageValueArray *aArguments, |
876 | | nsIVariant **_result) |
877 | 0 | { |
878 | 0 | nsAutoCString guid; |
879 | 0 | nsresult rv = GenerateGUID(guid); |
880 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
881 | 0 |
|
882 | 0 | NS_ADDREF(*_result = new UTF8TextVariant(guid)); |
883 | 0 | return NS_OK; |
884 | 0 | } |
885 | | |
886 | | //////////////////////////////////////////////////////////////////////////////// |
887 | | //// GUID Validation Function |
888 | | |
889 | | /* static */ |
890 | | nsresult |
891 | | IsValidGUIDFunction::create(mozIStorageConnection *aDBConn) |
892 | 0 | { |
893 | 0 | RefPtr<IsValidGUIDFunction> function = new IsValidGUIDFunction(); |
894 | 0 | return aDBConn->CreateFunction( |
895 | 0 | NS_LITERAL_CSTRING("is_valid_guid"), 1, function |
896 | 0 | ); |
897 | 0 | } |
898 | | |
899 | | NS_IMPL_ISUPPORTS( |
900 | | IsValidGUIDFunction, |
901 | | mozIStorageFunction |
902 | | ) |
903 | | |
904 | | NS_IMETHODIMP |
905 | | IsValidGUIDFunction::OnFunctionCall(mozIStorageValueArray *aArguments, |
906 | | nsIVariant **_result) |
907 | 0 | { |
908 | 0 | // Must have non-null function arguments. |
909 | 0 | MOZ_ASSERT(aArguments); |
910 | 0 |
|
911 | 0 | nsAutoCString guid; |
912 | 0 | aArguments->GetUTF8String(0, guid); |
913 | 0 |
|
914 | 0 | RefPtr<nsVariant> result = new nsVariant(); |
915 | 0 | result->SetAsBool(IsValidGUID(guid)); |
916 | 0 | result.forget(_result); |
917 | 0 | return NS_OK; |
918 | 0 | } |
919 | | |
920 | | //////////////////////////////////////////////////////////////////////////////// |
921 | | //// Get Unreversed Host Function |
922 | | |
923 | | /* static */ |
924 | | nsresult |
925 | | GetUnreversedHostFunction::create(mozIStorageConnection *aDBConn) |
926 | 0 | { |
927 | 0 | RefPtr<GetUnreversedHostFunction> function = new GetUnreversedHostFunction(); |
928 | 0 | nsresult rv = aDBConn->CreateFunction( |
929 | 0 | NS_LITERAL_CSTRING("get_unreversed_host"), 1, function |
930 | 0 | ); |
931 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
932 | 0 |
|
933 | 0 | return NS_OK; |
934 | 0 | } |
935 | | |
936 | | NS_IMPL_ISUPPORTS( |
937 | | GetUnreversedHostFunction, |
938 | | mozIStorageFunction |
939 | | ) |
940 | | |
941 | | NS_IMETHODIMP |
942 | | GetUnreversedHostFunction::OnFunctionCall(mozIStorageValueArray *aArguments, |
943 | | nsIVariant **_result) |
944 | 0 | { |
945 | 0 | // Must have non-null function arguments. |
946 | 0 | MOZ_ASSERT(aArguments); |
947 | 0 |
|
948 | 0 | nsAutoString src; |
949 | 0 | aArguments->GetString(0, src); |
950 | 0 |
|
951 | 0 | RefPtr<nsVariant> result = new nsVariant(); |
952 | 0 |
|
953 | 0 | if (src.Length()>1) { |
954 | 0 | src.Truncate(src.Length() - 1); |
955 | 0 | nsAutoString dest; |
956 | 0 | ReverseString(src, dest); |
957 | 0 | result->SetAsAString(dest); |
958 | 0 | } |
959 | 0 | else { |
960 | 0 | result->SetAsAString(EmptyString()); |
961 | 0 | } |
962 | 0 | result.forget(_result); |
963 | 0 | return NS_OK; |
964 | 0 | } |
965 | | |
966 | | //////////////////////////////////////////////////////////////////////////////// |
967 | | //// Fixup URL Function |
968 | | |
969 | | /* static */ |
970 | | nsresult |
971 | | FixupURLFunction::create(mozIStorageConnection *aDBConn) |
972 | 0 | { |
973 | 0 | RefPtr<FixupURLFunction> function = new FixupURLFunction(); |
974 | 0 | nsresult rv = aDBConn->CreateFunction( |
975 | 0 | NS_LITERAL_CSTRING("fixup_url"), 1, function |
976 | 0 | ); |
977 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
978 | 0 |
|
979 | 0 | return NS_OK; |
980 | 0 | } |
981 | | |
982 | | NS_IMPL_ISUPPORTS( |
983 | | FixupURLFunction, |
984 | | mozIStorageFunction |
985 | | ) |
986 | | |
987 | | NS_IMETHODIMP |
988 | | FixupURLFunction::OnFunctionCall(mozIStorageValueArray *aArguments, |
989 | | nsIVariant **_result) |
990 | 0 | { |
991 | 0 | // Must have non-null function arguments. |
992 | 0 | MOZ_ASSERT(aArguments); |
993 | 0 |
|
994 | 0 | nsAutoString src; |
995 | 0 | aArguments->GetString(0, src); |
996 | 0 |
|
997 | 0 | RefPtr<nsVariant> result = new nsVariant(); |
998 | 0 |
|
999 | 0 | if (StringBeginsWith(src, NS_LITERAL_STRING("http://"))) |
1000 | 0 | src.Cut(0, 7); |
1001 | 0 | else if (StringBeginsWith(src, NS_LITERAL_STRING("https://"))) |
1002 | 0 | src.Cut(0, 8); |
1003 | 0 | else if (StringBeginsWith(src, NS_LITERAL_STRING("ftp://"))) |
1004 | 0 | src.Cut(0, 6); |
1005 | 0 |
|
1006 | 0 | // Remove common URL hostname prefixes |
1007 | 0 | if (StringBeginsWith(src, NS_LITERAL_STRING("www."))) { |
1008 | 0 | src.Cut(0, 4); |
1009 | 0 | } |
1010 | 0 |
|
1011 | 0 | result->SetAsAString(src); |
1012 | 0 | result.forget(_result); |
1013 | 0 | return NS_OK; |
1014 | 0 | } |
1015 | | |
1016 | | //////////////////////////////////////////////////////////////////////////////// |
1017 | | //// Frecency Changed Notification Function |
1018 | | |
1019 | | /* static */ |
1020 | | nsresult |
1021 | | FrecencyNotificationFunction::create(mozIStorageConnection *aDBConn) |
1022 | 0 | { |
1023 | 0 | RefPtr<FrecencyNotificationFunction> function = |
1024 | 0 | new FrecencyNotificationFunction(); |
1025 | 0 | nsresult rv = aDBConn->CreateFunction( |
1026 | 0 | NS_LITERAL_CSTRING("notify_frecency"), 5, function |
1027 | 0 | ); |
1028 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1029 | 0 |
|
1030 | 0 | return NS_OK; |
1031 | 0 | } |
1032 | | |
1033 | | NS_IMPL_ISUPPORTS( |
1034 | | FrecencyNotificationFunction, |
1035 | | mozIStorageFunction |
1036 | | ) |
1037 | | |
1038 | | NS_IMETHODIMP |
1039 | | FrecencyNotificationFunction::OnFunctionCall(mozIStorageValueArray *aArgs, |
1040 | | nsIVariant **_result) |
1041 | 0 | { |
1042 | 0 | uint32_t numArgs; |
1043 | 0 | nsresult rv = aArgs->GetNumEntries(&numArgs); |
1044 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1045 | 0 | MOZ_ASSERT(numArgs == 5); |
1046 | 0 |
|
1047 | 0 | int32_t newFrecency = aArgs->AsInt32(0); |
1048 | 0 |
|
1049 | 0 | nsAutoCString spec; |
1050 | 0 | rv = aArgs->GetUTF8String(1, spec); |
1051 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1052 | 0 |
|
1053 | 0 | nsAutoCString guid; |
1054 | 0 | rv = aArgs->GetUTF8String(2, guid); |
1055 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1056 | 0 |
|
1057 | 0 | bool hidden = static_cast<bool>(aArgs->AsInt32(3)); |
1058 | 0 | PRTime lastVisitDate = static_cast<PRTime>(aArgs->AsInt64(4)); |
1059 | 0 |
|
1060 | 0 | const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService(); |
1061 | 0 | NS_ENSURE_STATE(navHistory); |
1062 | 0 | navHistory->DispatchFrecencyChangedNotification(spec, newFrecency, guid, |
1063 | 0 | hidden, lastVisitDate); |
1064 | 0 |
|
1065 | 0 | RefPtr<nsVariant> result = new nsVariant(); |
1066 | 0 | rv = result->SetAsInt32(newFrecency); |
1067 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1068 | 0 | result.forget(_result); |
1069 | 0 | return NS_OK; |
1070 | 0 | } |
1071 | | |
1072 | | //////////////////////////////////////////////////////////////////////////////// |
1073 | | //// Store Last Inserted Id Function |
1074 | | |
1075 | | /* static */ |
1076 | | nsresult |
1077 | | StoreLastInsertedIdFunction::create(mozIStorageConnection *aDBConn) |
1078 | 0 | { |
1079 | 0 | RefPtr<StoreLastInsertedIdFunction> function = |
1080 | 0 | new StoreLastInsertedIdFunction(); |
1081 | 0 | nsresult rv = aDBConn->CreateFunction( |
1082 | 0 | NS_LITERAL_CSTRING("store_last_inserted_id"), 2, function |
1083 | 0 | ); |
1084 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1085 | 0 |
|
1086 | 0 | return NS_OK; |
1087 | 0 | } |
1088 | | |
1089 | | NS_IMPL_ISUPPORTS( |
1090 | | StoreLastInsertedIdFunction, |
1091 | | mozIStorageFunction |
1092 | | ) |
1093 | | |
1094 | | NS_IMETHODIMP |
1095 | | StoreLastInsertedIdFunction::OnFunctionCall(mozIStorageValueArray *aArgs, |
1096 | | nsIVariant **_result) |
1097 | 0 | { |
1098 | 0 | uint32_t numArgs; |
1099 | 0 | nsresult rv = aArgs->GetNumEntries(&numArgs); |
1100 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1101 | 0 | MOZ_ASSERT(numArgs == 2); |
1102 | 0 |
|
1103 | 0 | nsAutoCString table; |
1104 | 0 | rv = aArgs->GetUTF8String(0, table); |
1105 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1106 | 0 |
|
1107 | 0 | int64_t lastInsertedId = aArgs->AsInt64(1); |
1108 | 0 |
|
1109 | 0 | MOZ_ASSERT(table.EqualsLiteral("moz_places") || |
1110 | 0 | table.EqualsLiteral("moz_historyvisits") || |
1111 | 0 | table.EqualsLiteral("moz_bookmarks") || |
1112 | 0 | table.EqualsLiteral("moz_icons")); |
1113 | 0 |
|
1114 | 0 | if (table.EqualsLiteral("moz_bookmarks")) { |
1115 | 0 | nsNavBookmarks::StoreLastInsertedId(table, lastInsertedId); |
1116 | 0 | } else if (table.EqualsLiteral("moz_icons")) { |
1117 | 0 | nsFaviconService::StoreLastInsertedId(table, lastInsertedId); |
1118 | 0 | } else { |
1119 | 0 | nsNavHistory::StoreLastInsertedId(table, lastInsertedId); |
1120 | 0 | } |
1121 | 0 |
|
1122 | 0 | RefPtr<nsVariant> result = new nsVariant(); |
1123 | 0 | rv = result->SetAsInt64(lastInsertedId); |
1124 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1125 | 0 | result.forget(_result); |
1126 | 0 | return NS_OK; |
1127 | 0 | } |
1128 | | |
1129 | | //////////////////////////////////////////////////////////////////////////////// |
1130 | | //// Get Query Param Function |
1131 | | |
1132 | | /* static */ |
1133 | | nsresult |
1134 | | GetQueryParamFunction::create(mozIStorageConnection *aDBConn) |
1135 | 0 | { |
1136 | 0 | RefPtr<GetQueryParamFunction> function = new GetQueryParamFunction(); |
1137 | 0 | return aDBConn->CreateFunction( |
1138 | 0 | NS_LITERAL_CSTRING("get_query_param"), 2, function |
1139 | 0 | ); |
1140 | 0 | } |
1141 | | |
1142 | | NS_IMPL_ISUPPORTS( |
1143 | | GetQueryParamFunction, |
1144 | | mozIStorageFunction |
1145 | | ) |
1146 | | |
1147 | | NS_IMETHODIMP |
1148 | | GetQueryParamFunction::OnFunctionCall(mozIStorageValueArray *aArguments, |
1149 | | nsIVariant **_result) |
1150 | 0 | { |
1151 | 0 | // Must have non-null function arguments. |
1152 | 0 | MOZ_ASSERT(aArguments); |
1153 | 0 |
|
1154 | 0 | nsDependentCString queryString = getSharedUTF8String(aArguments, 0); |
1155 | 0 | nsDependentCString paramName = getSharedUTF8String(aArguments, 1); |
1156 | 0 |
|
1157 | 0 | RefPtr<nsVariant> result = new nsVariant(); |
1158 | 0 | if (!queryString.IsEmpty() && !paramName.IsEmpty()) { |
1159 | 0 | GetQueryParamIterator iterator(paramName, result); |
1160 | 0 | URLParams::Parse(queryString, iterator); |
1161 | 0 | } |
1162 | 0 |
|
1163 | 0 | result.forget(_result); |
1164 | 0 | return NS_OK; |
1165 | 0 | } |
1166 | | |
1167 | | //////////////////////////////////////////////////////////////////////////////// |
1168 | | //// Hash Function |
1169 | | |
1170 | | /* static */ |
1171 | | nsresult |
1172 | | HashFunction::create(mozIStorageConnection *aDBConn) |
1173 | 0 | { |
1174 | 0 | RefPtr<HashFunction> function = new HashFunction(); |
1175 | 0 | return aDBConn->CreateFunction( |
1176 | 0 | NS_LITERAL_CSTRING("hash"), -1, function |
1177 | 0 | ); |
1178 | 0 | } |
1179 | | |
1180 | | NS_IMPL_ISUPPORTS( |
1181 | | HashFunction, |
1182 | | mozIStorageFunction |
1183 | | ) |
1184 | | |
1185 | | NS_IMETHODIMP |
1186 | | HashFunction::OnFunctionCall(mozIStorageValueArray *aArguments, |
1187 | | nsIVariant **_result) |
1188 | 0 | { |
1189 | 0 | // Must have non-null function arguments. |
1190 | 0 | MOZ_ASSERT(aArguments); |
1191 | 0 |
|
1192 | 0 | // Fetch arguments. Use default values if they were omitted. |
1193 | 0 | uint32_t numEntries; |
1194 | 0 | nsresult rv = aArguments->GetNumEntries(&numEntries); |
1195 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1196 | 0 | NS_ENSURE_TRUE(numEntries >= 1 && numEntries <= 2, NS_ERROR_FAILURE); |
1197 | 0 |
|
1198 | 0 | nsDependentCString str = getSharedUTF8String(aArguments, 0); |
1199 | 0 | nsAutoCString mode; |
1200 | 0 | if (numEntries > 1) { |
1201 | 0 | aArguments->GetUTF8String(1, mode); |
1202 | 0 | } |
1203 | 0 |
|
1204 | 0 | RefPtr<nsVariant> result = new nsVariant(); |
1205 | 0 | uint64_t hash; |
1206 | 0 | rv = HashURL(str, mode, &hash); |
1207 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1208 | 0 | rv = result->SetAsInt64(hash); |
1209 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1210 | 0 |
|
1211 | 0 | result.forget(_result); |
1212 | 0 | return NS_OK; |
1213 | 0 | } |
1214 | | |
1215 | | //////////////////////////////////////////////////////////////////////////////// |
1216 | | //// Get prefix function |
1217 | | |
1218 | | /* static */ |
1219 | | nsresult |
1220 | | GetPrefixFunction::create(mozIStorageConnection *aDBConn) |
1221 | 0 | { |
1222 | 0 | RefPtr<GetPrefixFunction> function = new GetPrefixFunction(); |
1223 | 0 | nsresult rv = aDBConn->CreateFunction( |
1224 | 0 | NS_LITERAL_CSTRING("get_prefix"), 1, function |
1225 | 0 | ); |
1226 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1227 | 0 |
|
1228 | 0 | return NS_OK; |
1229 | 0 | } |
1230 | | |
1231 | | NS_IMPL_ISUPPORTS( |
1232 | | GetPrefixFunction, |
1233 | | mozIStorageFunction |
1234 | | ) |
1235 | | |
1236 | | NS_IMETHODIMP |
1237 | | GetPrefixFunction::OnFunctionCall(mozIStorageValueArray *aArgs, |
1238 | | nsIVariant **_result) |
1239 | 0 | { |
1240 | 0 | MOZ_ASSERT(aArgs); |
1241 | 0 |
|
1242 | 0 | uint32_t numArgs; |
1243 | 0 | nsresult rv = aArgs->GetNumEntries(&numArgs); |
1244 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1245 | 0 | MOZ_ASSERT(numArgs == 1); |
1246 | 0 |
|
1247 | 0 | nsDependentCString spec(getSharedUTF8String(aArgs, 0)); |
1248 | 0 |
|
1249 | 0 | RefPtr<nsVariant> result = new nsVariant(); |
1250 | 0 | result->SetAsACString(Substring(spec, 0, getPrefixLength(spec))); |
1251 | 0 | result.forget(_result); |
1252 | 0 | return NS_OK; |
1253 | 0 | } |
1254 | | |
1255 | | //////////////////////////////////////////////////////////////////////////////// |
1256 | | //// Get host and port function |
1257 | | |
1258 | | /* static */ |
1259 | | nsresult |
1260 | | GetHostAndPortFunction::create(mozIStorageConnection *aDBConn) |
1261 | 0 | { |
1262 | 0 | RefPtr<GetHostAndPortFunction> function = new GetHostAndPortFunction(); |
1263 | 0 | nsresult rv = aDBConn->CreateFunction( |
1264 | 0 | NS_LITERAL_CSTRING("get_host_and_port"), 1, function |
1265 | 0 | ); |
1266 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1267 | 0 |
|
1268 | 0 | return NS_OK; |
1269 | 0 | } |
1270 | | |
1271 | | NS_IMPL_ISUPPORTS( |
1272 | | GetHostAndPortFunction, |
1273 | | mozIStorageFunction |
1274 | | ) |
1275 | | |
1276 | | NS_IMETHODIMP |
1277 | | GetHostAndPortFunction::OnFunctionCall(mozIStorageValueArray *aArgs, |
1278 | | nsIVariant **_result) |
1279 | 0 | { |
1280 | 0 | MOZ_ASSERT(aArgs); |
1281 | 0 |
|
1282 | 0 | uint32_t numArgs; |
1283 | 0 | nsresult rv = aArgs->GetNumEntries(&numArgs); |
1284 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1285 | 0 | MOZ_ASSERT(numArgs == 1); |
1286 | 0 |
|
1287 | 0 | nsDependentCString spec(getSharedUTF8String(aArgs, 0)); |
1288 | 0 |
|
1289 | 0 | RefPtr<nsVariant> result = new nsVariant(); |
1290 | 0 |
|
1291 | 0 | size_type length; |
1292 | 0 | size_type index = indexOfHostAndPort(spec, &length); |
1293 | 0 | result->SetAsACString(Substring(spec, index, length)); |
1294 | 0 | result.forget(_result); |
1295 | 0 | return NS_OK; |
1296 | 0 | } |
1297 | | |
1298 | | //////////////////////////////////////////////////////////////////////////////// |
1299 | | //// Strip prefix and userinfo function |
1300 | | |
1301 | | /* static */ |
1302 | | nsresult |
1303 | | StripPrefixAndUserinfoFunction::create(mozIStorageConnection *aDBConn) |
1304 | 0 | { |
1305 | 0 | RefPtr<StripPrefixAndUserinfoFunction> function = |
1306 | 0 | new StripPrefixAndUserinfoFunction(); |
1307 | 0 | nsresult rv = aDBConn->CreateFunction( |
1308 | 0 | NS_LITERAL_CSTRING("strip_prefix_and_userinfo"), 1, function |
1309 | 0 | ); |
1310 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1311 | 0 |
|
1312 | 0 | return NS_OK; |
1313 | 0 | } |
1314 | | |
1315 | | NS_IMPL_ISUPPORTS( |
1316 | | StripPrefixAndUserinfoFunction, |
1317 | | mozIStorageFunction |
1318 | | ) |
1319 | | |
1320 | | NS_IMETHODIMP |
1321 | | StripPrefixAndUserinfoFunction::OnFunctionCall(mozIStorageValueArray *aArgs, |
1322 | | nsIVariant **_result) |
1323 | 0 | { |
1324 | 0 | MOZ_ASSERT(aArgs); |
1325 | 0 |
|
1326 | 0 | uint32_t numArgs; |
1327 | 0 | nsresult rv = aArgs->GetNumEntries(&numArgs); |
1328 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1329 | 0 | MOZ_ASSERT(numArgs == 1); |
1330 | 0 |
|
1331 | 0 | nsDependentCString spec(getSharedUTF8String(aArgs, 0)); |
1332 | 0 |
|
1333 | 0 | RefPtr<nsVariant> result = new nsVariant(); |
1334 | 0 |
|
1335 | 0 | size_type index = indexOfHostAndPort(spec, NULL); |
1336 | 0 | result->SetAsACString(Substring(spec, index, spec.Length() - index)); |
1337 | 0 | result.forget(_result); |
1338 | 0 | return NS_OK; |
1339 | 0 | } |
1340 | | |
1341 | | //////////////////////////////////////////////////////////////////////////////// |
1342 | | //// Is frecency decaying function |
1343 | | |
1344 | | /* static */ |
1345 | | nsresult |
1346 | | IsFrecencyDecayingFunction::create(mozIStorageConnection *aDBConn) |
1347 | 0 | { |
1348 | 0 | RefPtr<IsFrecencyDecayingFunction> function = |
1349 | 0 | new IsFrecencyDecayingFunction(); |
1350 | 0 | nsresult rv = aDBConn->CreateFunction( |
1351 | 0 | NS_LITERAL_CSTRING("is_frecency_decaying"), 0, function |
1352 | 0 | ); |
1353 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1354 | 0 |
|
1355 | 0 | return NS_OK; |
1356 | 0 | } |
1357 | | |
1358 | | NS_IMPL_ISUPPORTS( |
1359 | | IsFrecencyDecayingFunction, |
1360 | | mozIStorageFunction |
1361 | | ) |
1362 | | |
1363 | | NS_IMETHODIMP |
1364 | | IsFrecencyDecayingFunction::OnFunctionCall(mozIStorageValueArray *aArgs, |
1365 | | nsIVariant **_result) |
1366 | 0 | { |
1367 | 0 | MOZ_ASSERT(aArgs); |
1368 | 0 |
|
1369 | 0 | uint32_t numArgs; |
1370 | 0 | nsresult rv = aArgs->GetNumEntries(&numArgs); |
1371 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1372 | 0 | MOZ_ASSERT(numArgs == 0); |
1373 | 0 |
|
1374 | 0 | const nsNavHistory *navHistory = nsNavHistory::GetConstHistoryService(); |
1375 | 0 | NS_ENSURE_STATE(navHistory); |
1376 | 0 |
|
1377 | 0 | RefPtr<nsVariant> result = new nsVariant(); |
1378 | 0 | rv = result->SetAsBool(navHistory->IsFrecencyDecaying()); |
1379 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1380 | 0 | result.forget(_result); |
1381 | 0 | return NS_OK; |
1382 | 0 | } |
1383 | | |
1384 | | //////////////////////////////////////////////////////////////////////////////// |
1385 | | //// sqrt function |
1386 | | |
1387 | | /* static */ |
1388 | | nsresult |
1389 | | SqrtFunction::create(mozIStorageConnection *aDBConn) |
1390 | 0 | { |
1391 | 0 | RefPtr<SqrtFunction> function = new SqrtFunction(); |
1392 | 0 | nsresult rv = aDBConn->CreateFunction( |
1393 | 0 | NS_LITERAL_CSTRING("sqrt"), 1, function |
1394 | 0 | ); |
1395 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1396 | 0 | return NS_OK; |
1397 | 0 | } |
1398 | | |
1399 | | NS_IMPL_ISUPPORTS( |
1400 | | SqrtFunction, |
1401 | | mozIStorageFunction |
1402 | | ) |
1403 | | |
1404 | | NS_IMETHODIMP |
1405 | | SqrtFunction::OnFunctionCall(mozIStorageValueArray *aArgs, |
1406 | | nsIVariant **_result) |
1407 | 0 | { |
1408 | 0 | MOZ_ASSERT(aArgs); |
1409 | 0 |
|
1410 | 0 | uint32_t numArgs; |
1411 | 0 | nsresult rv = aArgs->GetNumEntries(&numArgs); |
1412 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1413 | 0 | MOZ_ASSERT(numArgs == 1); |
1414 | 0 |
|
1415 | 0 | double value = aArgs->AsDouble(0); |
1416 | 0 |
|
1417 | 0 | RefPtr<nsVariant> result = new nsVariant(); |
1418 | 0 | rv = result->SetAsDouble(sqrt(value)); |
1419 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1420 | 0 | result.forget(_result); |
1421 | 0 |
|
1422 | 0 | return NS_OK; |
1423 | 0 | } |
1424 | | |
1425 | | //////////////////////////////////////////////////////////////////////////////// |
1426 | | //// Note Sync Change Function |
1427 | | |
1428 | | /* static */ |
1429 | | nsresult |
1430 | | NoteSyncChangeFunction::create(mozIStorageConnection *aDBConn) |
1431 | 0 | { |
1432 | 0 | RefPtr<NoteSyncChangeFunction> function = |
1433 | 0 | new NoteSyncChangeFunction(); |
1434 | 0 | nsresult rv = aDBConn->CreateFunction( |
1435 | 0 | NS_LITERAL_CSTRING("note_sync_change"), 0, function |
1436 | 0 | ); |
1437 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1438 | 0 |
|
1439 | 0 | return NS_OK; |
1440 | 0 | } |
1441 | | |
1442 | | NS_IMPL_ISUPPORTS( |
1443 | | NoteSyncChangeFunction, |
1444 | | mozIStorageFunction |
1445 | | ) |
1446 | | |
1447 | | NS_IMETHODIMP |
1448 | | NoteSyncChangeFunction::OnFunctionCall(mozIStorageValueArray *aArgs, |
1449 | | nsIVariant **_result) |
1450 | 0 | { |
1451 | 0 | nsNavBookmarks::NoteSyncChange(); |
1452 | 0 | *_result = nullptr; |
1453 | 0 | return NS_OK; |
1454 | 0 | } |
1455 | | |
1456 | | } // namespace places |
1457 | | } // namespace mozilla |