[WTF] Clean up StringStatics.cpp by using LazyNeverDestroyed<> for Atoms
[WebKit-https.git] / Source / WebCore / html / Autofill.cpp
1 /*
2  * Copyright (C) 2016 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "Autofill.h"
28
29 #include "HTMLFormControlElement.h"
30 #include "HTMLFormElement.h"
31 #include "HTMLNames.h"
32 #include <wtf/HashMap.h>
33 #include <wtf/NeverDestroyed.h>
34 #include <wtf/text/AtomicString.h>
35 #include <wtf/text/AtomicStringHash.h>
36
37 namespace WebCore {
38
39 enum class AutofillCategory {
40     Off,
41     Automatic,
42     Normal,
43     Contact,
44 };
45
46 struct AutofillInfo {
47     AutofillFieldName fieldName;
48     AutofillCategory category;
49 };
50
51 static HashMap<AtomicString, AutofillInfo>& fieldNameMap()
52 {
53     static NeverDestroyed<HashMap<AtomicString, AutofillInfo>> map;
54     if (map.get().isEmpty()) {
55         map.get().add(AtomicString("off", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::None, AutofillCategory::Off });
56         map.get().add(AtomicString("on", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::None,  AutofillCategory::Automatic });
57         map.get().add(AtomicString("name", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Name, AutofillCategory::Normal });
58         map.get().add(AtomicString("honorific-prefix", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::HonorificPrefix, AutofillCategory::Normal });
59         map.get().add(AtomicString("given-name", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::GivenName, AutofillCategory::Normal });
60         map.get().add(AtomicString("additional-name", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::AdditionalName, AutofillCategory::Normal });
61         map.get().add(AtomicString("family-name", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::FamilyName, AutofillCategory::Normal });
62         map.get().add(AtomicString("honorific-suffix", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::HonorificSuffix, AutofillCategory::Normal });
63         map.get().add(AtomicString("nickname", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Nickname, AutofillCategory::Normal });
64         map.get().add(AtomicString("username", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Username, AutofillCategory::Normal });
65         map.get().add(AtomicString("new-password", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::NewPassword, AutofillCategory::Normal });
66         map.get().add(AtomicString("current-password", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CurrentPassword, AutofillCategory::Normal });
67         map.get().add(AtomicString("organization-title", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::OrganizationTitle, AutofillCategory::Normal });
68         map.get().add(AtomicString("organization", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Organization, AutofillCategory::Normal });
69         map.get().add(AtomicString("street-address", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::StreetAddress, AutofillCategory::Normal });
70         map.get().add(AtomicString("address-line1", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::AddressLine1, AutofillCategory::Normal });
71         map.get().add(AtomicString("address-line2", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::AddressLine2, AutofillCategory::Normal });
72         map.get().add(AtomicString("address-line3", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::AddressLine3, AutofillCategory::Normal });
73         map.get().add(AtomicString("address-level4", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::AddressLevel4, AutofillCategory::Normal });
74         map.get().add(AtomicString("address-level3", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::AddressLevel3, AutofillCategory::Normal });
75         map.get().add(AtomicString("address-level2", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::AddressLevel2, AutofillCategory::Normal });
76         map.get().add(AtomicString("address-level1", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::AddressLevel1, AutofillCategory::Normal });
77         map.get().add(AtomicString("country", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Country, AutofillCategory::Normal });
78         map.get().add(AtomicString("country-name", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CountryName, AutofillCategory::Normal });
79         map.get().add(AtomicString("postal-code", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::PostalCode, AutofillCategory::Normal });
80         map.get().add(AtomicString("cc-name", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CcName, AutofillCategory::Normal });
81         map.get().add(AtomicString("cc-given-name", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CcGivenName, AutofillCategory::Normal });
82         map.get().add(AtomicString("cc-additional-name", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CcAdditionalName, AutofillCategory::Normal });
83         map.get().add(AtomicString("cc-family-name", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CcFamilyName, AutofillCategory::Normal });
84         map.get().add(AtomicString("cc-number", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CcNumber, AutofillCategory::Normal });
85         map.get().add(AtomicString("cc-exp", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CcExp, AutofillCategory::Normal });
86         map.get().add(AtomicString("cc-exp-month", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CcExpMonth, AutofillCategory::Normal });
87         map.get().add(AtomicString("cc-exp-year", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CcExpYear, AutofillCategory::Normal });
88         map.get().add(AtomicString("cc-csc", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CcCsc, AutofillCategory::Normal });
89         map.get().add(AtomicString("cc-type", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::CcType, AutofillCategory::Normal });
90         map.get().add(AtomicString("transaction-currency", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::TransactionCurrency, AutofillCategory::Normal });
91         map.get().add(AtomicString("transaction-amount", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::TransactionAmount, AutofillCategory::Normal });
92         map.get().add(AtomicString("language", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Language, AutofillCategory::Normal });
93         map.get().add(AtomicString("bday", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Bday, AutofillCategory::Normal });
94         map.get().add(AtomicString("bday-day", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::BdayDay, AutofillCategory::Normal });
95         map.get().add(AtomicString("bday-month", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::BdayMonth, AutofillCategory::Normal });
96         map.get().add(AtomicString("bday-year", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::BdayYear, AutofillCategory::Normal });
97         map.get().add(AtomicString("sex", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Sex, AutofillCategory::Normal });
98         map.get().add(AtomicString("url", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::URL, AutofillCategory::Normal });
99         map.get().add(AtomicString("photo", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Photo, AutofillCategory::Normal });
100
101         map.get().add(AtomicString("tel", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Tel, AutofillCategory::Contact });
102         map.get().add(AtomicString("tel-country-code", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::TelCountryCode, AutofillCategory::Contact });
103         map.get().add(AtomicString("tel-national", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::TelNational, AutofillCategory::Contact });
104         map.get().add(AtomicString("tel-area-code", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::TelAreaCode, AutofillCategory::Contact });
105         map.get().add(AtomicString("tel-local", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::TelLocal, AutofillCategory::Contact });
106         map.get().add(AtomicString("tel-local-prefix", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::TelLocalPrefix, AutofillCategory::Contact });
107         map.get().add(AtomicString("tel-local-suffix", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::TelLocalSuffix, AutofillCategory::Contact });
108         map.get().add(AtomicString("tel-extension", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::TelExtension, AutofillCategory::Contact });
109         map.get().add(AtomicString("email", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Email, AutofillCategory::Contact });
110         map.get().add(AtomicString("impp", AtomicString::ConstructFromLiteral), AutofillInfo{ AutofillFieldName::Impp, AutofillCategory::Contact });
111     }
112
113     return map;
114 }
115
116 AutofillFieldName toAutofillFieldName(const AtomicString& value)
117 {
118     auto map = fieldNameMap();
119     auto it = map.find(value);
120     if (it == map.end())
121         return AutofillFieldName::None;
122     return it->value.fieldName;
123 }
124
125 static inline bool isContactToken(const AtomicString& token)
126 {
127     static NeverDestroyed<AtomicString> home("home", AtomicString::ConstructFromLiteral);
128     static NeverDestroyed<AtomicString> work("work", AtomicString::ConstructFromLiteral);
129     static NeverDestroyed<AtomicString> mobile("mobile", AtomicString::ConstructFromLiteral);
130     static NeverDestroyed<AtomicString> fax("fax", AtomicString::ConstructFromLiteral);
131     static NeverDestroyed<AtomicString> pager("pager", AtomicString::ConstructFromLiteral);
132
133     return token == home || token == work || token == mobile || token == fax || token == pager;
134 }
135
136 static unsigned maxTokensForAutofillFieldCategory(AutofillCategory category)
137 {
138     switch (category) {
139     case AutofillCategory::Automatic:
140     case AutofillCategory::Off:
141         return 1;
142
143     case AutofillCategory::Normal:
144         return 3;
145
146     case AutofillCategory::Contact:
147         return 4;
148     }
149     ASSERT_NOT_REACHED();
150     return 0;
151 }
152
153 // https://html.spec.whatwg.org/multipage/forms.html#processing-model-3
154 AutofillData AutofillData::createFromHTMLFormControlElement(const HTMLFormControlElement& element)
155 {
156     static NeverDestroyed<AtomicString> on("on", AtomicString::ConstructFromLiteral);
157     static NeverDestroyed<AtomicString> off("off", AtomicString::ConstructFromLiteral);
158
159     // Label: Default
160     // 26. Let the element's IDL-exposed autofill value be the empty string, and its autofill hint set and autofill scope be empty.
161     // 27. If the element's autocomplete attribute is wearing the autofill anchor mantle, then let the element's autofill field name be the empty string and abort these steps.
162     // 28. Let form be the element's form owner, if any, or null otherwise.
163     // 29. If form is not null and form's autocomplete attribute is in the off state, then let the element's autofill field name be "off". Otherwise, let the element's autofill field name be "on".
164     auto defaultLabel = [&] () -> AutofillData {
165         if (element.autofillMantle() == AutofillMantle::Anchor)
166             return { emptyString(), emptyString() };
167         
168         auto form = element.form();
169         if (form && form->autocomplete() == off)
170             return { off, emptyString() };
171         return { on, emptyString() };
172     };
173
174
175     const AtomicString& attributeValue = element.attributeWithoutSynchronization(HTMLNames::autocompleteAttr);
176
177     // 1. If the element has no autocomplete attribute, then jump to the step labeled default.
178     if (attributeValue == nullAtom())
179         return defaultLabel();
180
181     // 2. Let tokens be the result of splitting the attribute's value on spaces.
182     SpaceSplitString tokens(attributeValue, true);
183
184     // 3. If tokens is empty, then jump to the step labeled default.
185     if (tokens.isEmpty())
186         return defaultLabel();
187     
188     // 4. Let index be the index of the last token in tokens
189     unsigned index = tokens.size() - 1;
190
191     // 5. If the indexth token in tokens is not an ASCII case-insensitive match for one of the
192     // tokens given in the first column of the following table, or if the number of tokens in
193     // tokens is greater than the maximum number given in the cell in the second column of that
194     // token's row, then jump to the step labeled default. Otherwise, let field be the string given
195     // in the cell of the first column of the matching row, and let category be the value of the
196     // cell in the third column of that same row.
197     auto& map = fieldNameMap();
198
199     auto it = map.find(tokens[index]);
200     if (it == map.end())
201         return defaultLabel();
202     
203     auto category = it->value.category;
204
205     if (tokens.size() > maxTokensForAutofillFieldCategory(category))
206         return defaultLabel();
207
208     const auto& field = tokens[index];
209
210     // 6. If category is Off or Automatic but the element's autocomplete attribute is wearing the
211     // autofill anchor mantle, then jump to the step labeled default.
212     auto mantle = element.autofillMantle();
213     if ((category == AutofillCategory::Off || category == AutofillCategory::Automatic) && mantle == AutofillMantle::Anchor)
214         return defaultLabel();
215
216     // 7. If category is Off, let the element's autofill field name be the string "off", let its
217     // autofill hint set be empty, and let its IDL-exposed autofill value be the string "off".
218     // Then, abort these steps.
219     if (category == AutofillCategory::Off)
220         return { off, off.get().string() };
221
222     // 8. If category is Automatic, let the element's autofill field name be the string "on",
223     // let its autofill hint set be empty, and let its IDL-exposed autofill value be the string
224     // "on". Then, abort these steps.
225     if (category == AutofillCategory::Automatic)
226         return { on, on.get().string() };
227
228     // 9. Let scope tokens be an empty list.
229     // 10. Let hint tokens be an empty set.
230
231     // NOTE: We are ignoring these steps as we don't currently make use of scope tokens or hint tokens anywhere.
232
233     // 11. Let IDL value have the same value as field.
234     String idlValue = field;
235
236     // 12, If the indexth token in tokens is the first entry, then skip to the step labeled done.
237     if (index == 0)
238         return { field, idlValue };
239
240     // 13. Decrement index by one
241     index--;
242
243     // 14. If category is Contact and the indexth token in tokens is an ASCII case-insensitive match
244     // for one of the strings in the following list, then run the substeps that follow:
245     const auto& contactToken = tokens[index];
246     if (category == AutofillCategory::Contact && isContactToken(contactToken)) {
247         // 1. Let contact be the matching string from the list above.
248         const auto& contact = contactToken;
249
250         // 2. Insert contact at the start of scope tokens.
251         // 3. Add contact to hint tokens.
252
253         // NOTE: We are ignoring these steps as we don't currently make use of scope tokens or hint tokens anywhere.
254
255         // 4. Let IDL value be the concatenation of contact, a U+0020 SPACE character, and the previous
256         // value of IDL value (which at this point will always be field).
257         idlValue = WTF::makeString(contact, " ", idlValue);
258
259         // 5. If the indexth entry in tokens is the first entry, then skip to the step labeled done.
260         if (index == 0)
261             return { field, idlValue };
262
263         // 6. Decrement index by one.
264         index--;
265     }
266
267     // 15. If the indexth token in tokens is an ASCII case-insensitive match for one of the strings
268     // in the following list, then run the substeps that follow:
269     const auto& modeToken = tokens[index];
270     if (equalIgnoringASCIICase(modeToken, "shipping") || equalIgnoringASCIICase(modeToken, "billing")) {
271         // 1. Let mode be the matching string from the list above.
272         const auto& mode = modeToken;
273
274         // 2. Insert mode at the start of scope tokens.
275         // 3. Add mode to hint tokens.
276
277         // NOTE: We are ignoring these steps as we don't currently make use of scope tokens or hint tokens anywhere.
278
279         // 4. Let IDL value be the concatenation of mode, a U+0020 SPACE character, and the previous
280         // value of IDL value (which at this point will either be field or the concatenation of contact,
281         // a space, and field).
282         idlValue = WTF::makeString(mode, " ", idlValue);
283
284         // 5. If the indexth entry in tokens is the first entry, then skip to the step labeled done.
285         if (index == 0)
286             return { field, idlValue };
287
288         // 6. Decrement index by one.
289         index--;
290     }
291
292     // 16. If the indexth entry in tokens is not the first entry, then jump to the step labeled default.
293     if (index != 0)
294         return defaultLabel();
295
296     // 17. If the first eight characters of the indexth token in tokens are not an ASCII case-insensitive
297     // match for the string "section-", then jump to the step labeled default.
298     const auto& sectionToken = tokens[index];
299     if (!sectionToken.startsWithIgnoringASCIICase("section-"))
300         return defaultLabel();
301
302     // 18. Let section be the indexth token in tokens, converted to ASCII lowercase.
303     const auto& section = sectionToken;
304
305     // 19. Insert section at the start of scope tokens.
306
307     // NOTE: We are ignoring this step as we don't currently make use of scope tokens or hint tokens anywhere.
308
309     // 20. Let IDL value be the concatenation of section, a U+0020 SPACE character, and the previous
310     // value of IDL value.
311     idlValue = WTF::makeString(section, " ", idlValue);
312
313     return { field, idlValue };
314 }
315
316 } // namespace WebCore