Fix <rdar://5624866> CFStringRef UI_STRING should use a cache and follow...
[WebKit-https.git] / WebKit / win / WebLocalizableStrings.cpp
1 /*
2  * Copyright (C) 2006, 2007 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 COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #include "WebKitDLL.h"
28
29 #include "WebLocalizableStrings.h"
30
31 #pragma warning(push, 0)
32 #include <WebCore/CString.h>
33 #include <WebCore/PlatformString.h>
34 #include <WebCore/StringHash.h>
35 #pragma warning(pop)
36
37 #include <wtf/Assertions.h>
38 #include <wtf/HashMap.h>
39 #include <wtf/RetainPtr.h>
40 #include <CoreFoundation/CoreFoundation.h>
41
42 class LocalizedString;
43
44 using namespace WebCore;
45
46 WebLocalizableStringsBundle WebKitLocalizableStringsBundle = { "com.apple.WebKit", 0 };
47
48 static HashMap<String, LocalizedString*> mainBundleLocStrings;
49 static HashMap<String, LocalizedString*> frameworkLocStrings;
50
51 class LocalizedString : Noncopyable {
52 public:
53     LocalizedString(CFStringRef string)
54         : m_cfString(string)
55     {
56         ASSERT_ARG(string, string);
57     }
58
59     operator LPCTSTR() const;
60     operator CFStringRef() const { return m_cfString; }
61
62 private:
63     CFStringRef m_cfString;
64     mutable String m_string;
65 };
66
67 LocalizedString::operator LPCTSTR() const
68 {
69     if (!m_string.isEmpty())
70         return m_string.charactersWithNullTermination();
71
72     m_string = m_cfString;
73
74     for (unsigned int i = 1; i < m_string.length(); i++)
75         if (m_string[i] == '@' && (m_string[i - 1] == '%' || (i > 2 && m_string[i - 1] == '$' && m_string[i - 2] >= '1' && m_string[i - 2] <= '9' && m_string[i - 3] == '%')))
76             m_string.replace(i, 1, "s");
77
78     return m_string.charactersWithNullTermination();
79 }
80
81 static CFBundleRef createWebKitBundle()
82 {
83     static CFBundleRef bundle;
84     static bool initialized;
85
86     if (initialized)
87         return bundle;
88     initialized = true;
89
90     WCHAR pathStr[MAX_PATH];
91     DWORD length = ::GetModuleFileNameW(gInstance, pathStr, MAX_PATH);
92     if (!length || (length == MAX_PATH && GetLastError() == ERROR_INSUFFICIENT_BUFFER))
93         return 0;
94
95     bool found = false;
96     for (int i = length - 1; i >= 0; i--) {
97         // warning C6385: Invalid data: accessing 'pathStr', the readable size is '520' bytes, but '2000' bytes might be read
98         #pragma warning(suppress: 6385)
99         if (pathStr[i] == L'\\') {
100             // warning C6386: Buffer overrun: accessing 'pathStr', the writable size is '520' bytes, but '1996' bytes might be written
101             #pragma warning(suppress: 6386)
102             pathStr[i] = 0;
103             found = true;
104             break;
105         }
106     }
107     if (!found)
108         return 0;
109
110     if (wcscat_s(pathStr, MAX_PATH, L"\\WebKit.resources"))
111         return 0;
112
113     String bundlePathString(pathStr);
114     CFStringRef bundlePathCFString = bundlePathString.createCFString();
115     if (!bundlePathCFString)
116         return 0;
117
118     CFURLRef bundleURLRef = CFURLCreateWithFileSystemPath(0, bundlePathCFString, kCFURLWindowsPathStyle, true);
119     CFRelease(bundlePathCFString);
120     if (!bundleURLRef)
121         return 0;
122
123     bundle = CFBundleCreate(0, bundleURLRef);
124     CFRelease(bundleURLRef);
125     return bundle;
126 }
127
128 static CFBundleRef cfBundleForStringsBundle(WebLocalizableStringsBundle* stringsBundle)
129 {
130     if (!stringsBundle) {
131         static CFBundleRef mainBundle = CFBundleGetMainBundle();
132         return mainBundle;
133     }
134
135     createWebKitBundle();
136
137     if (!stringsBundle->bundle)
138         stringsBundle->bundle = CFBundleGetBundleWithIdentifier(RetainPtr<CFStringRef>(AdoptCF, CFStringCreateWithCString(0, stringsBundle->identifier, kCFStringEncodingASCII)).get());
139     return stringsBundle->bundle;
140 }
141
142 static CFStringRef copyLocalizedStringFromBundle(WebLocalizableStringsBundle* stringsBundle, const String& key)
143 {
144     static CFStringRef notFound = CFSTR("localized string not found");
145
146     CFBundleRef bundle = cfBundleForStringsBundle(stringsBundle);
147     if (!bundle)
148         return notFound;
149
150     RetainPtr<CFStringRef> keyString(AdoptCF, key.createCFString());
151     CFStringRef result = CFCopyLocalizedStringWithDefaultValue(keyString.get(), 0, bundle, notFound, 0);
152
153     ASSERT_WITH_MESSAGE(result != notFound, "could not find localizable string %s in bundle", key);
154     return result;
155 }
156
157 static LocalizedString* findCachedString(WebLocalizableStringsBundle* stringsBundle, const String& key)
158 {
159     if (!stringsBundle)
160         return mainBundleLocStrings.get(key);
161
162     if (stringsBundle->bundle == WebKitLocalizableStringsBundle.bundle)
163         return frameworkLocStrings.get(key);
164
165     return 0;
166 }
167
168 static void cacheString(WebLocalizableStringsBundle* stringsBundle, const String& key, LocalizedString* value)
169 {
170     if (!stringsBundle) {
171         mainBundleLocStrings.set(key, value);
172         return;
173     }
174
175     frameworkLocStrings.set(key, value);
176 }
177
178 static const LocalizedString& localizedString(WebLocalizableStringsBundle* stringsBundle, const String& key)
179 {
180     LocalizedString* string = findCachedString(stringsBundle, key);
181     if (string)
182         return *string;
183
184     string = new LocalizedString(copyLocalizedStringFromBundle(stringsBundle, key));
185     cacheString(stringsBundle, key, string);
186
187     return *string;
188 }
189
190 CFStringRef WebLocalizedStringUTF8(WebLocalizableStringsBundle* stringsBundle, LPCSTR key)
191 {
192     if (!key)
193         return 0;
194
195     return localizedString(stringsBundle, String::fromUTF8(key));
196 }
197
198 LPCTSTR WebLocalizedLPCTSTRUTF8(WebLocalizableStringsBundle* stringsBundle, LPCSTR key)
199 {
200     if (!key)
201         return 0;
202
203     return localizedString(stringsBundle, String::fromUTF8(key));
204 }
205
206 // These functions are deprecated.
207
208 CFStringRef WebLocalizedString(WebLocalizableStringsBundle* stringsBundle, LPCTSTR key)
209 {
210     if (!key)
211         return 0;
212
213     return localizedString(stringsBundle, String(key));
214 }
215
216 LPCTSTR WebLocalizedLPCTSTR(WebLocalizableStringsBundle* stringsBundle, LPCTSTR key)
217 {
218     if (!key)
219         return 0;
220
221     return localizedString(stringsBundle, String(key));
222 }
223
224 void SetWebLocalizedStringMainBundle(CFBundleRef)
225 {
226 }