[WTF] Remove StaticLock
[WebKit.git] / Source / WTF / wtf / Language.cpp
1 /*
2  * Copyright (C) 2010, 2013, 2016, 2017 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 "Language.h"
28
29 #include <wtf/HashMap.h>
30 #include <wtf/NeverDestroyed.h>
31 #include <wtf/RetainPtr.h>
32 #include <wtf/text/WTFString.h>
33
34 #if USE(CF) && !PLATFORM(WIN)
35 #include <CoreFoundation/CoreFoundation.h>
36 #endif
37
38 namespace WTF {
39
40 static Lock userPreferredLanguagesMutex;
41
42 typedef HashMap<void*, LanguageChangeObserverFunction> ObserverMap;
43 static ObserverMap& observerMap()
44 {
45     static NeverDestroyed<ObserverMap> map;
46     return map.get();
47 }
48
49 void addLanguageChangeObserver(void* context, LanguageChangeObserverFunction customObserver)
50 {
51     observerMap().set(context, customObserver);
52 }
53
54 void removeLanguageChangeObserver(void* context)
55 {
56     ASSERT(observerMap().contains(context));
57     observerMap().remove(context);
58 }
59
60 void languageDidChange()
61 {
62     ObserverMap::iterator end = observerMap().end();
63     for (ObserverMap::iterator iter = observerMap().begin(); iter != end; ++iter)
64         iter->value(iter->key);
65 }
66
67 String defaultLanguage()
68 {
69     Vector<String> languages = userPreferredLanguages();
70     if (languages.size())
71         return languages[0];
72
73     return emptyString();
74 }
75
76 static Vector<String>& preferredLanguagesOverride()
77 {
78     static NeverDestroyed<Vector<String>> override;
79     return override;
80 }
81
82 Vector<String> userPreferredLanguagesOverride()
83 {
84     return preferredLanguagesOverride();
85 }
86
87 void overrideUserPreferredLanguages(const Vector<String>& override)
88 {
89     preferredLanguagesOverride() = override;
90     languageDidChange();
91 }
92
93 static Vector<String> isolatedCopy(const Vector<String>& strings)
94 {
95     Vector<String> copy;
96     copy.reserveInitialCapacity(strings.size());
97     for (auto& language : strings)
98         copy.uncheckedAppend(language.isolatedCopy());
99     return copy;
100 }
101
102 Vector<String> userPreferredLanguages()
103 {
104     {
105         std::lock_guard<Lock> lock(userPreferredLanguagesMutex);
106         Vector<String>& override = preferredLanguagesOverride();
107         if (!override.isEmpty())
108             return isolatedCopy(override);
109     }
110
111     return platformUserPreferredLanguages();
112 }
113
114 static String canonicalLanguageIdentifier(const String& languageCode)
115 {
116     String lowercaseLanguageCode = languageCode.convertToASCIILowercase();
117     
118     if (lowercaseLanguageCode.length() >= 3 && lowercaseLanguageCode[2] == '_')
119         lowercaseLanguageCode.replace(2, 1, "-");
120
121     return lowercaseLanguageCode;
122 }
123
124 size_t indexOfBestMatchingLanguageInList(const String& language, const Vector<String>& languageList, bool& exactMatch)
125 {
126     String lowercaseLanguage = language.convertToASCIILowercase();
127     String languageWithoutLocaleMatch;
128     String languageMatchButNotLocale;
129     size_t languageWithoutLocaleMatchIndex = 0;
130     size_t languageMatchButNotLocaleMatchIndex = 0;
131     bool canMatchLanguageOnly = (lowercaseLanguage.length() == 2 || (lowercaseLanguage.length() >= 3 && lowercaseLanguage[2] == '-'));
132
133     for (size_t i = 0; i < languageList.size(); ++i) {
134         String canonicalizedLanguageFromList = canonicalLanguageIdentifier(languageList[i]);
135
136         if (lowercaseLanguage == canonicalizedLanguageFromList) {
137             exactMatch = true;
138             return i;
139         }
140
141         if (canMatchLanguageOnly && canonicalizedLanguageFromList.length() >= 2) {
142             if (lowercaseLanguage[0] == canonicalizedLanguageFromList[0] && lowercaseLanguage[1] == canonicalizedLanguageFromList[1]) {
143                 if (!languageWithoutLocaleMatch.length() && canonicalizedLanguageFromList.length() == 2) {
144                     languageWithoutLocaleMatch = languageList[i];
145                     languageWithoutLocaleMatchIndex = i;
146                 }
147                 if (!languageMatchButNotLocale.length() && canonicalizedLanguageFromList.length() >= 3) {
148                     languageMatchButNotLocale = languageList[i];
149                     languageMatchButNotLocaleMatchIndex = i;
150                 }
151             }
152         }
153     }
154
155     exactMatch = false;
156
157     // If we have both a language-only match and a languge-but-not-locale match, return the
158     // languge-only match as is considered a "better" match. For example, if the list
159     // provided has both "en-GB" and "en" and the user prefers "en-US" we will return "en".
160     if (languageWithoutLocaleMatch.length())
161         return languageWithoutLocaleMatchIndex;
162
163     if (languageMatchButNotLocale.length())
164         return languageMatchButNotLocaleMatchIndex;
165
166     return languageList.size();
167 }
168
169 String displayNameForLanguageLocale(const String& localeName)
170 {
171 #if USE(CF) && !PLATFORM(WIN)
172     if (!localeName.isEmpty())
173         return adoptCF(CFLocaleCopyDisplayNameForPropertyValue(adoptCF(CFLocaleCopyCurrent()).get(), kCFLocaleIdentifier, localeName.createCFString().get())).get();
174 #endif
175     return localeName;
176 }
177
178 }