Home | History | Annotate | Download | only in extensions
      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