1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/common/extensions/extension_message_bundle.h"
6
7 #include <string>
8 #include <vector>
9
10 #include "base/i18n/rtl.h"
11 #include "base/memory/linked_ptr.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/string_util.h"
14 #include "base/utf_string_conversions.h"
15 #include "base/values.h"
16 #include "chrome/common/extensions/extension_constants.h"
17 #include "chrome/common/extensions/extension_error_utils.h"
18 #include "chrome/common/extensions/extension_l10n_util.h"
19 #include "testing/gtest/include/gtest/gtest.h"
20
21 namespace errors = extension_manifest_errors;
22
23 class ExtensionMessageBundleTest : public testing::Test {
24 protected:
25 enum BadDictionary {
26 INVALID_NAME,
27 NAME_NOT_A_TREE,
28 EMPTY_NAME_TREE,
29 MISSING_MESSAGE,
30 PLACEHOLDER_NOT_A_TREE,
31 EMPTY_PLACEHOLDER_TREE,
32 CONTENT_MISSING,
33 MESSAGE_PLACEHOLDER_DOESNT_MATCH,
34 };
35
36 // Helper method for dictionary building.
SetDictionary(const std::string & name,DictionaryValue * subtree,DictionaryValue * target)37 void SetDictionary(const std::string& name,
38 DictionaryValue* subtree,
39 DictionaryValue* target) {
40 target->Set(name, static_cast<Value*>(subtree));
41 }
42
CreateContentTree(const std::string & name,const std::string & content,DictionaryValue * dict)43 void CreateContentTree(const std::string& name,
44 const std::string& content,
45 DictionaryValue* dict) {
46 DictionaryValue* content_tree = new DictionaryValue;
47 content_tree->SetString(ExtensionMessageBundle::kContentKey, content);
48 SetDictionary(name, content_tree, dict);
49 }
50
CreatePlaceholdersTree(DictionaryValue * dict)51 void CreatePlaceholdersTree(DictionaryValue* dict) {
52 DictionaryValue* placeholders_tree = new DictionaryValue;
53 CreateContentTree("a", "A", placeholders_tree);
54 CreateContentTree("b", "B", placeholders_tree);
55 CreateContentTree("c", "C", placeholders_tree);
56 SetDictionary(ExtensionMessageBundle::kPlaceholdersKey,
57 placeholders_tree,
58 dict);
59 }
60
CreateMessageTree(const std::string & name,const std::string & message,bool create_placeholder_subtree,DictionaryValue * dict)61 void CreateMessageTree(const std::string& name,
62 const std::string& message,
63 bool create_placeholder_subtree,
64 DictionaryValue* dict) {
65 DictionaryValue* message_tree = new DictionaryValue;
66 if (create_placeholder_subtree)
67 CreatePlaceholdersTree(message_tree);
68 message_tree->SetString(ExtensionMessageBundle::kMessageKey, message);
69 SetDictionary(name, message_tree, dict);
70 }
71
72 // Caller owns the memory.
CreateGoodDictionary()73 DictionaryValue* CreateGoodDictionary() {
74 DictionaryValue* dict = new DictionaryValue;
75 CreateMessageTree("n1", "message1 $a$ $b$", true, dict);
76 CreateMessageTree("n2", "message2 $c$", true, dict);
77 CreateMessageTree("n3", "message3", false, dict);
78 return dict;
79 }
80
81 // Caller owns the memory.
CreateBadDictionary(enum BadDictionary what_is_bad)82 DictionaryValue* CreateBadDictionary(enum BadDictionary what_is_bad) {
83 DictionaryValue* dict = CreateGoodDictionary();
84 // Now remove/break things.
85 switch (what_is_bad) {
86 case INVALID_NAME:
87 CreateMessageTree("n 5", "nevermind", false, dict);
88 break;
89 case NAME_NOT_A_TREE:
90 dict->SetString("n4", "whatever");
91 break;
92 case EMPTY_NAME_TREE: {
93 DictionaryValue* empty_tree = new DictionaryValue;
94 SetDictionary("n4", empty_tree, dict);
95 }
96 break;
97 case MISSING_MESSAGE:
98 dict->Remove("n1.message", NULL);
99 break;
100 case PLACEHOLDER_NOT_A_TREE:
101 dict->SetString("n1.placeholders", "whatever");
102 break;
103 case EMPTY_PLACEHOLDER_TREE: {
104 DictionaryValue* empty_tree = new DictionaryValue;
105 SetDictionary("n1.placeholders", empty_tree, dict);
106 }
107 break;
108 case CONTENT_MISSING:
109 dict->Remove("n1.placeholders.a.content", NULL);
110 break;
111 case MESSAGE_PLACEHOLDER_DOESNT_MATCH:
112 DictionaryValue* value;
113 dict->Remove("n1.placeholders.a", NULL);
114 dict->GetDictionary("n1.placeholders", &value);
115 CreateContentTree("x", "X", value);
116 break;
117 }
118
119 return dict;
120 }
121
ReservedMessagesCount()122 unsigned int ReservedMessagesCount() {
123 // Update when adding new reserved messages.
124 return 5U;
125 }
126
CheckReservedMessages(ExtensionMessageBundle * handler)127 void CheckReservedMessages(ExtensionMessageBundle* handler) {
128 std::string ui_locale = extension_l10n_util::CurrentLocaleOrDefault();
129 EXPECT_EQ(ui_locale,
130 handler->GetL10nMessage(ExtensionMessageBundle::kUILocaleKey));
131
132 std::string text_dir = "ltr";
133 if (base::i18n::GetTextDirectionForLocale(ui_locale.c_str()) ==
134 base::i18n::RIGHT_TO_LEFT)
135 text_dir = "rtl";
136
137 EXPECT_EQ(text_dir, handler->GetL10nMessage(
138 ExtensionMessageBundle::kBidiDirectionKey));
139 }
140
AppendReservedMessages(const std::string & application_locale)141 bool AppendReservedMessages(const std::string& application_locale) {
142 std::string error;
143 return handler_->AppendReservedMessagesForLocale(
144 application_locale, &error);
145 }
146
CreateMessageBundle()147 std::string CreateMessageBundle() {
148 std::string error;
149 handler_.reset(ExtensionMessageBundle::Create(catalogs_, &error));
150
151 return error;
152 }
153
ClearDictionary()154 void ClearDictionary() {
155 handler_->dictionary_.clear();
156 }
157
158 scoped_ptr<ExtensionMessageBundle> handler_;
159 std::vector<linked_ptr<DictionaryValue> > catalogs_;
160 };
161
TEST_F(ExtensionMessageBundleTest,ReservedMessagesCount)162 TEST_F(ExtensionMessageBundleTest, ReservedMessagesCount) {
163 ASSERT_EQ(5U, ReservedMessagesCount());
164 }
165
TEST_F(ExtensionMessageBundleTest,InitEmptyDictionaries)166 TEST_F(ExtensionMessageBundleTest, InitEmptyDictionaries) {
167 CreateMessageBundle();
168 EXPECT_TRUE(handler_.get() != NULL);
169 EXPECT_EQ(0U + ReservedMessagesCount(), handler_->size());
170 CheckReservedMessages(handler_.get());
171 }
172
TEST_F(ExtensionMessageBundleTest,InitGoodDefaultDict)173 TEST_F(ExtensionMessageBundleTest, InitGoodDefaultDict) {
174 catalogs_.push_back(linked_ptr<DictionaryValue>(CreateGoodDictionary()));
175 CreateMessageBundle();
176
177 EXPECT_TRUE(handler_.get() != NULL);
178 EXPECT_EQ(3U + ReservedMessagesCount(), handler_->size());
179
180 EXPECT_EQ("message1 A B", handler_->GetL10nMessage("n1"));
181 EXPECT_EQ("message2 C", handler_->GetL10nMessage("n2"));
182 EXPECT_EQ("message3", handler_->GetL10nMessage("n3"));
183 CheckReservedMessages(handler_.get());
184 }
185
TEST_F(ExtensionMessageBundleTest,InitAppDictConsultedFirst)186 TEST_F(ExtensionMessageBundleTest, InitAppDictConsultedFirst) {
187 catalogs_.push_back(linked_ptr<DictionaryValue>(CreateGoodDictionary()));
188 catalogs_.push_back(linked_ptr<DictionaryValue>(CreateGoodDictionary()));
189
190 DictionaryValue* app_dict = catalogs_[0].get();
191 // Flip placeholders in message of n1 tree.
192 app_dict->SetString("n1.message", "message1 $b$ $a$");
193 // Remove one message from app dict.
194 app_dict->Remove("n2", NULL);
195 // Replace n3 with N3.
196 app_dict->Remove("n3", NULL);
197 CreateMessageTree("N3", "message3_app_dict", false, app_dict);
198
199 CreateMessageBundle();
200
201 EXPECT_TRUE(handler_.get() != NULL);
202 EXPECT_EQ(3U + ReservedMessagesCount(), handler_->size());
203
204 EXPECT_EQ("message1 B A", handler_->GetL10nMessage("n1"));
205 EXPECT_EQ("message2 C", handler_->GetL10nMessage("n2"));
206 EXPECT_EQ("message3_app_dict", handler_->GetL10nMessage("n3"));
207 CheckReservedMessages(handler_.get());
208 }
209
TEST_F(ExtensionMessageBundleTest,InitBadAppDict)210 TEST_F(ExtensionMessageBundleTest, InitBadAppDict) {
211 catalogs_.push_back(
212 linked_ptr<DictionaryValue>(CreateBadDictionary(INVALID_NAME)));
213 catalogs_.push_back(linked_ptr<DictionaryValue>(CreateGoodDictionary()));
214
215 std::string error = CreateMessageBundle();
216
217 EXPECT_TRUE(handler_.get() == NULL);
218 EXPECT_EQ("Name of a key \"n 5\" is invalid. Only ASCII [a-z], "
219 "[A-Z], [0-9] and \"_\" are allowed.", error);
220
221 catalogs_[0].reset(CreateBadDictionary(NAME_NOT_A_TREE));
222 handler_.reset(ExtensionMessageBundle::Create(catalogs_, &error));
223 EXPECT_TRUE(handler_.get() == NULL);
224 EXPECT_EQ("Not a valid tree for key n4.", error);
225
226 catalogs_[0].reset(CreateBadDictionary(EMPTY_NAME_TREE));
227 handler_.reset(ExtensionMessageBundle::Create(catalogs_, &error));
228 EXPECT_TRUE(handler_.get() == NULL);
229 EXPECT_EQ("There is no \"message\" element for key n4.", error);
230
231 catalogs_[0].reset(CreateBadDictionary(MISSING_MESSAGE));
232 handler_.reset(ExtensionMessageBundle::Create(catalogs_, &error));
233 EXPECT_TRUE(handler_.get() == NULL);
234 EXPECT_EQ("There is no \"message\" element for key n1.", error);
235
236 catalogs_[0].reset(CreateBadDictionary(PLACEHOLDER_NOT_A_TREE));
237 handler_.reset(ExtensionMessageBundle::Create(catalogs_, &error));
238 EXPECT_TRUE(handler_.get() == NULL);
239 EXPECT_EQ("Not a valid \"placeholders\" element for key n1.", error);
240
241 catalogs_[0].reset(CreateBadDictionary(EMPTY_PLACEHOLDER_TREE));
242 handler_.reset(ExtensionMessageBundle::Create(catalogs_, &error));
243 EXPECT_TRUE(handler_.get() == NULL);
244 EXPECT_EQ("Variable $a$ used but not defined.", error);
245
246 catalogs_[0].reset(CreateBadDictionary(CONTENT_MISSING));
247 handler_.reset(ExtensionMessageBundle::Create(catalogs_, &error));
248 EXPECT_TRUE(handler_.get() == NULL);
249 EXPECT_EQ("Invalid \"content\" element for key n1.", error);
250
251 catalogs_[0].reset(CreateBadDictionary(MESSAGE_PLACEHOLDER_DOESNT_MATCH));
252 handler_.reset(ExtensionMessageBundle::Create(catalogs_, &error));
253 EXPECT_TRUE(handler_.get() == NULL);
254 EXPECT_EQ("Variable $a$ used but not defined.", error);
255 }
256
TEST_F(ExtensionMessageBundleTest,ReservedMessagesOverrideDeveloperMessages)257 TEST_F(ExtensionMessageBundleTest, ReservedMessagesOverrideDeveloperMessages) {
258 catalogs_.push_back(linked_ptr<DictionaryValue>(CreateGoodDictionary()));
259
260 DictionaryValue* dict = catalogs_[0].get();
261 CreateMessageTree(ExtensionMessageBundle::kUILocaleKey, "x", false, dict);
262
263 std::string error = CreateMessageBundle();
264
265 EXPECT_TRUE(handler_.get() == NULL);
266 std::string expected_error = ExtensionErrorUtils::FormatErrorMessage(
267 errors::kReservedMessageFound, ExtensionMessageBundle::kUILocaleKey);
268 EXPECT_EQ(expected_error, error);
269 }
270
TEST_F(ExtensionMessageBundleTest,AppendReservedMessagesForLTR)271 TEST_F(ExtensionMessageBundleTest, AppendReservedMessagesForLTR) {
272 CreateMessageBundle();
273
274 ASSERT_TRUE(handler_.get() != NULL);
275 ClearDictionary();
276 ASSERT_TRUE(AppendReservedMessages("en_US"));
277
278 EXPECT_EQ("en_US",
279 handler_->GetL10nMessage(ExtensionMessageBundle::kUILocaleKey));
280 EXPECT_EQ("ltr", handler_->GetL10nMessage(
281 ExtensionMessageBundle::kBidiDirectionKey));
282 EXPECT_EQ("rtl", handler_->GetL10nMessage(
283 ExtensionMessageBundle::kBidiReversedDirectionKey));
284 EXPECT_EQ("left", handler_->GetL10nMessage(
285 ExtensionMessageBundle::kBidiStartEdgeKey));
286 EXPECT_EQ("right", handler_->GetL10nMessage(
287 ExtensionMessageBundle::kBidiEndEdgeKey));
288 }
289
TEST_F(ExtensionMessageBundleTest,AppendReservedMessagesForRTL)290 TEST_F(ExtensionMessageBundleTest, AppendReservedMessagesForRTL) {
291 CreateMessageBundle();
292
293 ASSERT_TRUE(handler_.get() != NULL);
294 ClearDictionary();
295 ASSERT_TRUE(AppendReservedMessages("he"));
296
297 EXPECT_EQ("he",
298 handler_->GetL10nMessage(ExtensionMessageBundle::kUILocaleKey));
299 EXPECT_EQ("rtl", handler_->GetL10nMessage(
300 ExtensionMessageBundle::kBidiDirectionKey));
301 EXPECT_EQ("ltr", handler_->GetL10nMessage(
302 ExtensionMessageBundle::kBidiReversedDirectionKey));
303 EXPECT_EQ("right", handler_->GetL10nMessage(
304 ExtensionMessageBundle::kBidiStartEdgeKey));
305 EXPECT_EQ("left", handler_->GetL10nMessage(
306 ExtensionMessageBundle::kBidiEndEdgeKey));
307 }
308
TEST_F(ExtensionMessageBundleTest,IsValidNameCheckValidCharacters)309 TEST_F(ExtensionMessageBundleTest, IsValidNameCheckValidCharacters) {
310 EXPECT_TRUE(ExtensionMessageBundle::IsValidName(std::string("a__BV_9")));
311 EXPECT_TRUE(ExtensionMessageBundle::IsValidName(std::string("@@a__BV_9")));
312 EXPECT_FALSE(ExtensionMessageBundle::IsValidName(std::string("$a__BV_9$")));
313 EXPECT_FALSE(ExtensionMessageBundle::IsValidName(std::string("a-BV-9")));
314 EXPECT_FALSE(ExtensionMessageBundle::IsValidName(std::string("a#BV!9")));
315 EXPECT_FALSE(ExtensionMessageBundle::IsValidName(std::string("a<b")));
316 }
317
318 struct ReplaceVariables {
319 const char* original;
320 const char* result;
321 const char* error;
322 const char* begin_delimiter;
323 const char* end_delimiter;
324 bool pass;
325 };
326
TEST(ExtensionMessageBundle,ReplaceMessagesInText)327 TEST(ExtensionMessageBundle, ReplaceMessagesInText) {
328 const char* kMessageBegin = ExtensionMessageBundle::kMessageBegin;
329 const char* kMessageEnd = ExtensionMessageBundle::kMessageEnd;
330 const char* kPlaceholderBegin = ExtensionMessageBundle::kPlaceholderBegin;
331 const char* kPlaceholderEnd = ExtensionMessageBundle::kPlaceholderEnd;
332
333 static ReplaceVariables test_cases[] = {
334 // Message replacement.
335 { "This is __MSG_siMPle__ message", "This is simple message",
336 "", kMessageBegin, kMessageEnd, true },
337 { "This is __MSG_", "This is __MSG_",
338 "", kMessageBegin, kMessageEnd, true },
339 { "This is __MSG__simple__ message", "This is __MSG__simple__ message",
340 "Variable __MSG__simple__ used but not defined.",
341 kMessageBegin, kMessageEnd, false },
342 { "__MSG_LoNg__", "A pretty long replacement",
343 "", kMessageBegin, kMessageEnd, true },
344 { "A __MSG_SimpLE__MSG_ a", "A simpleMSG_ a",
345 "", kMessageBegin, kMessageEnd, true },
346 { "A __MSG_simple__MSG_long__", "A simpleMSG_long__",
347 "", kMessageBegin, kMessageEnd, true },
348 { "A __MSG_simple____MSG_long__", "A simpleA pretty long replacement",
349 "", kMessageBegin, kMessageEnd, true },
350 { "__MSG_d1g1ts_are_ok__", "I are d1g1t",
351 "", kMessageBegin, kMessageEnd, true },
352 // Placeholder replacement.
353 { "This is $sImpLe$ message", "This is simple message",
354 "", kPlaceholderBegin, kPlaceholderEnd, true },
355 { "This is $", "This is $",
356 "", kPlaceholderBegin, kPlaceholderEnd, true },
357 { "This is $$sIMPle$ message", "This is $simple message",
358 "", kPlaceholderBegin, kPlaceholderEnd, true },
359 { "$LONG_V$", "A pretty long replacement",
360 "", kPlaceholderBegin, kPlaceholderEnd, true },
361 { "A $simple$$ a", "A simple$ a",
362 "", kPlaceholderBegin, kPlaceholderEnd, true },
363 { "A $simple$long_v$", "A simplelong_v$",
364 "", kPlaceholderBegin, kPlaceholderEnd, true },
365 { "A $simple$$long_v$", "A simpleA pretty long replacement",
366 "", kPlaceholderBegin, kPlaceholderEnd, true },
367 { "This is $bad name$", "This is $bad name$",
368 "", kPlaceholderBegin, kPlaceholderEnd, true },
369 { "This is $missing$", "This is $missing$",
370 "Variable $missing$ used but not defined.",
371 kPlaceholderBegin, kPlaceholderEnd, false },
372 };
373
374 ExtensionMessageBundle::SubstitutionMap messages;
375 messages.insert(std::make_pair("simple", "simple"));
376 messages.insert(std::make_pair("long", "A pretty long replacement"));
377 messages.insert(std::make_pair("long_v", "A pretty long replacement"));
378 messages.insert(std::make_pair("bad name", "Doesn't matter"));
379 messages.insert(std::make_pair("d1g1ts_are_ok", "I are d1g1t"));
380
381 for (size_t i = 0; i < arraysize(test_cases); ++i) {
382 std::string text = test_cases[i].original;
383 std::string error;
384 EXPECT_EQ(test_cases[i].pass,
385 ExtensionMessageBundle::ReplaceVariables(messages,
386 test_cases[i].begin_delimiter,
387 test_cases[i].end_delimiter,
388 &text,
389 &error));
390 EXPECT_EQ(test_cases[i].result, text);
391 }
392 }
393
394 ///////////////////////////////////////////////////////////////////////////////
395 //
396 // Renderer helper functions test.
397 //
398 ///////////////////////////////////////////////////////////////////////////////
399
TEST(GetExtensionToL10nMessagesMapTest,ReturnsTheSameObject)400 TEST(GetExtensionToL10nMessagesMapTest, ReturnsTheSameObject) {
401 ExtensionToL10nMessagesMap* map1 = GetExtensionToL10nMessagesMap();
402 ASSERT_TRUE(NULL != map1);
403
404 ExtensionToL10nMessagesMap* map2 = GetExtensionToL10nMessagesMap();
405 ASSERT_EQ(map1, map2);
406 }
407
TEST(GetExtensionToL10nMessagesMapTest,ReturnsNullForUnknownExtensionId)408 TEST(GetExtensionToL10nMessagesMapTest, ReturnsNullForUnknownExtensionId) {
409 const std::string extension_id("some_unique_12334212314234_id");
410 L10nMessagesMap* map = GetL10nMessagesMap(extension_id);
411 EXPECT_TRUE(NULL == map);
412 }
413
TEST(GetExtensionToL10nMessagesMapTest,ReturnsMapForKnownExtensionId)414 TEST(GetExtensionToL10nMessagesMapTest, ReturnsMapForKnownExtensionId) {
415 const std::string extension_id("some_unique_121212121212121_id");
416 // Store a map for given id.
417 L10nMessagesMap messages;
418 messages.insert(std::make_pair("message_name", "message_value"));
419 (*GetExtensionToL10nMessagesMap())[extension_id] = messages;
420
421 L10nMessagesMap* map = GetL10nMessagesMap(extension_id);
422 ASSERT_TRUE(NULL != map);
423 EXPECT_EQ(1U, map->size());
424 EXPECT_EQ("message_value", (*map)["message_name"]);
425 }
426