• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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