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