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. 37 void SetDictionary(const std::string& name, 38 DictionaryValue* subtree, 39 DictionaryValue* target) { 40 target->Set(name, static_cast<Value*>(subtree)); 41 } 42 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 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 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. 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. 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 122 unsigned int ReservedMessagesCount() { 123 // Update when adding new reserved messages. 124 return 5U; 125 } 126 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 141 bool AppendReservedMessages(const std::string& application_locale) { 142 std::string error; 143 return handler_->AppendReservedMessagesForLocale( 144 application_locale, &error); 145 } 146 147 std::string CreateMessageBundle() { 148 std::string error; 149 handler_.reset(ExtensionMessageBundle::Create(catalogs_, &error)); 150 151 return error; 152 } 153 154 void ClearDictionary() { 155 handler_->dictionary_.clear(); 156 } 157 158 scoped_ptr<ExtensionMessageBundle> handler_; 159 std::vector<linked_ptr<DictionaryValue> > catalogs_; 160 }; 161 162 TEST_F(ExtensionMessageBundleTest, ReservedMessagesCount) { 163 ASSERT_EQ(5U, ReservedMessagesCount()); 164 } 165 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 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 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 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 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 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 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 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 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 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 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 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