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/command.h"
      6 
      7 #include "base/memory/scoped_ptr.h"
      8 #include "base/strings/string_number_conversions.h"
      9 #include "base/strings/string_util.h"
     10 #include "base/strings/utf_string_conversions.h"
     11 #include "base/values.h"
     12 #include "testing/gtest/include/gtest/gtest.h"
     13 
     14 class CommandTest : public testing::Test {
     15 };
     16 
     17 typedef const struct {
     18   bool expected_result;
     19   ui::Accelerator accelerator;
     20   const char* command_name;
     21   const char* key;
     22   const char* description;
     23 } ConstCommandsTestData;
     24 
     25 // Checks the |suggested_key| value parses into a command when specified as a
     26 // string or dictionary of platform specific keys. If
     27 // |platform_specific_only| is true, only the latter is tested. |platforms|
     28 // specifies all platforms to use when populating the |suggested_key|
     29 // dictionary.
     30 void CheckParse(ConstCommandsTestData data,
     31                 int i,
     32                 bool platform_specific_only,
     33                 std::vector<std::string>& platforms) {
     34   SCOPED_TRACE(std::string("Command name: |") + data.command_name + "| key: |" +
     35                data.key + "| description: |" + data.description + "| index: " +
     36                base::IntToString(i));
     37 
     38   extensions::Command command;
     39   scoped_ptr<base::DictionaryValue> input(new base::DictionaryValue);
     40   base::string16 error;
     41 
     42   // First, test the parse of a string suggested_key value.
     43   input->SetString("suggested_key", data.key);
     44   input->SetString("description", data.description);
     45 
     46   if (!platform_specific_only) {
     47     bool result = command.Parse(input.get(), data.command_name, i, &error);
     48     EXPECT_EQ(data.expected_result, result);
     49     if (result) {
     50       EXPECT_STREQ(data.description,
     51                    base::UTF16ToASCII(command.description()).c_str());
     52       EXPECT_STREQ(data.command_name, command.command_name().c_str());
     53       EXPECT_EQ(data.accelerator, command.accelerator());
     54     }
     55   }
     56 
     57   // Now, test the parse of a platform dictionary suggested_key value.
     58   if (data.key[0] != '\0') {
     59     std::string current_platform = extensions::Command::CommandPlatform();
     60     if (platform_specific_only &&
     61         std::find(platforms.begin(), platforms.end(), current_platform) ==
     62             platforms.end()) {
     63       // Given a |current_platform| without a |suggested_key|, |default| is
     64       // used. However, some keys, such as Search on Chrome OS, are only valid
     65       // for platform specific entries. Skip the test in this case.
     66       return;
     67     }
     68 
     69     input.reset(new base::DictionaryValue);
     70     base::DictionaryValue* key_dict = new base::DictionaryValue();
     71 
     72     for (size_t j = 0; j < platforms.size(); ++j)
     73       key_dict->SetString(platforms[j], data.key);
     74 
     75     input->Set("suggested_key", key_dict);
     76     input->SetString("description", data.description);
     77 
     78     bool result = command.Parse(input.get(), data.command_name, i, &error);
     79     EXPECT_EQ(data.expected_result, result);
     80 
     81     if (result) {
     82       EXPECT_STREQ(data.description,
     83                    base::UTF16ToASCII(command.description()).c_str());
     84       EXPECT_STREQ(data.command_name, command.command_name().c_str());
     85       EXPECT_EQ(data.accelerator, command.accelerator());
     86     }
     87   }
     88 }
     89 
     90 TEST(CommandTest, ExtensionCommandParsing) {
     91   const ui::Accelerator none = ui::Accelerator();
     92   const ui::Accelerator shift_f = ui::Accelerator(ui::VKEY_F,
     93                                                   ui::EF_SHIFT_DOWN);
     94 #if defined(OS_MACOSX)
     95   int ctrl = ui::EF_COMMAND_DOWN;
     96 #else
     97   int ctrl = ui::EF_CONTROL_DOWN;
     98 #endif
     99 
    100   const ui::Accelerator ctrl_f = ui::Accelerator(ui::VKEY_F, ctrl);
    101   const ui::Accelerator alt_f = ui::Accelerator(ui::VKEY_F, ui::EF_ALT_DOWN);
    102   const ui::Accelerator ctrl_shift_f =
    103       ui::Accelerator(ui::VKEY_F, ctrl | ui::EF_SHIFT_DOWN);
    104   const ui::Accelerator alt_shift_f =
    105       ui::Accelerator(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN);
    106   const ui::Accelerator ctrl_1 = ui::Accelerator(ui::VKEY_1, ctrl);
    107   const ui::Accelerator ctrl_comma = ui::Accelerator(ui::VKEY_OEM_COMMA, ctrl);
    108   const ui::Accelerator ctrl_dot = ui::Accelerator(ui::VKEY_OEM_PERIOD, ctrl);
    109   const ui::Accelerator ctrl_left = ui::Accelerator(ui::VKEY_LEFT, ctrl);
    110   const ui::Accelerator ctrl_right = ui::Accelerator(ui::VKEY_RIGHT, ctrl);
    111   const ui::Accelerator ctrl_up = ui::Accelerator(ui::VKEY_UP, ctrl);
    112   const ui::Accelerator ctrl_down = ui::Accelerator(ui::VKEY_DOWN, ctrl);
    113   const ui::Accelerator ctrl_ins = ui::Accelerator(ui::VKEY_INSERT, ctrl);
    114   const ui::Accelerator ctrl_del = ui::Accelerator(ui::VKEY_DELETE, ctrl);
    115   const ui::Accelerator ctrl_home = ui::Accelerator(ui::VKEY_HOME, ctrl);
    116   const ui::Accelerator ctrl_end = ui::Accelerator(ui::VKEY_END, ctrl);
    117   const ui::Accelerator ctrl_pgup = ui::Accelerator(ui::VKEY_PRIOR, ctrl);
    118   const ui::Accelerator ctrl_pgdwn = ui::Accelerator(ui::VKEY_NEXT, ctrl);
    119   const ui::Accelerator next_track =
    120       ui::Accelerator(ui::VKEY_MEDIA_NEXT_TRACK, ui::EF_NONE);
    121   const ui::Accelerator prev_track =
    122       ui::Accelerator(ui::VKEY_MEDIA_PREV_TRACK, ui::EF_NONE);
    123   const ui::Accelerator play_pause =
    124       ui::Accelerator(ui::VKEY_MEDIA_PLAY_PAUSE, ui::EF_NONE);
    125   const ui::Accelerator stop =
    126       ui::Accelerator(ui::VKEY_MEDIA_STOP, ui::EF_NONE);
    127 
    128   ConstCommandsTestData kTests[] = {
    129       // Negative test (one or more missing required fields). We don't need to
    130       // test |command_name| being blank as it is used as a key in the manifest,
    131       // so it can't be blank (and we CHECK() when it is). A blank shortcut is
    132       // permitted.
    133       {false, none, "command", "", ""},
    134       {false, none, "command", "Ctrl+f", ""},
    135       // Ctrl+Alt is not permitted, see MSDN link in comments in Parse function.
    136       {false, none, "command", "Ctrl+Alt+F", "description"},
    137       // Unsupported shortcuts/too many, or missing modifier.
    138       {false, none, "command", "A", "description"},
    139       {false, none, "command", "F10", "description"},
    140       {false, none, "command", "Ctrl+F+G", "description"},
    141       {false, none, "command", "Ctrl+Alt+Shift+G", "description"},
    142       // Shift on its own is not supported.
    143       {false, shift_f, "command", "Shift+F", "description"},
    144       {false, shift_f, "command", "F+Shift", "description"},
    145       // Basic tests.
    146       {true, none, "command", "", "description"},
    147       {true, ctrl_f, "command", "Ctrl+F", "description"},
    148       {true, alt_f, "command", "Alt+F", "description"},
    149       {true, ctrl_shift_f, "command", "Ctrl+Shift+F", "description"},
    150       {true, alt_shift_f, "command", "Alt+Shift+F", "description"},
    151       {true, ctrl_1, "command", "Ctrl+1", "description"},
    152       // Shortcut token order tests.
    153       {true, ctrl_f, "command", "F+Ctrl", "description"},
    154       {true, alt_f, "command", "F+Alt", "description"},
    155       {true, ctrl_shift_f, "command", "F+Ctrl+Shift", "description"},
    156       {true, ctrl_shift_f, "command", "F+Shift+Ctrl", "description"},
    157       {true, alt_shift_f, "command", "F+Alt+Shift", "description"},
    158       {true, alt_shift_f, "command", "F+Shift+Alt", "description"},
    159       // Case insensitivity is not OK.
    160       {false, ctrl_f, "command", "Ctrl+f", "description"},
    161       {false, ctrl_f, "command", "cTrL+F", "description"},
    162       // Skipping description is OK for browser- and pageActions.
    163       {true, ctrl_f, "_execute_browser_action", "Ctrl+F", ""},
    164       {true, ctrl_f, "_execute_page_action", "Ctrl+F", ""},
    165       // Home, End, Arrow keys, etc.
    166       {true, ctrl_comma, "_execute_browser_action", "Ctrl+Comma", ""},
    167       {true, ctrl_dot, "_execute_browser_action", "Ctrl+Period", ""},
    168       {true, ctrl_left, "_execute_browser_action", "Ctrl+Left", ""},
    169       {true, ctrl_right, "_execute_browser_action", "Ctrl+Right", ""},
    170       {true, ctrl_up, "_execute_browser_action", "Ctrl+Up", ""},
    171       {true, ctrl_down, "_execute_browser_action", "Ctrl+Down", ""},
    172       {true, ctrl_ins, "_execute_browser_action", "Ctrl+Insert", ""},
    173       {true, ctrl_del, "_execute_browser_action", "Ctrl+Delete", ""},
    174       {true, ctrl_home, "_execute_browser_action", "Ctrl+Home", ""},
    175       {true, ctrl_end, "_execute_browser_action", "Ctrl+End", ""},
    176       {true, ctrl_pgup, "_execute_browser_action", "Ctrl+PageUp", ""},
    177       {true, ctrl_pgdwn, "_execute_browser_action", "Ctrl+PageDown", ""},
    178       // Media keys.
    179       {true, next_track, "command", "MediaNextTrack", "description"},
    180       {true, play_pause, "command", "MediaPlayPause", "description"},
    181       {true, prev_track, "command", "MediaPrevTrack", "description"},
    182       {true, stop, "command", "MediaStop", "description"},
    183       {false, none, "_execute_browser_action", "MediaNextTrack", ""},
    184       {false, none, "_execute_page_action", "MediaPrevTrack", ""},
    185       {false, none, "command", "Ctrl+Shift+MediaPrevTrack", "description"},
    186   };
    187   std::vector<std::string> all_platforms;
    188   all_platforms.push_back("default");
    189   all_platforms.push_back("chromeos");
    190   all_platforms.push_back("linux");
    191   all_platforms.push_back("mac");
    192   all_platforms.push_back("windows");
    193 
    194   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i)
    195     CheckParse(kTests[i], i, false, all_platforms);
    196 }
    197 
    198 TEST(CommandTest, ExtensionCommandParsingFallback) {
    199   std::string description = "desc";
    200   std::string command_name = "foo";
    201 
    202   // Test that platform specific keys are honored on each platform, despite
    203   // fallback being given.
    204   scoped_ptr<base::DictionaryValue> input(new base::DictionaryValue);
    205   base::DictionaryValue* key_dict = new base::DictionaryValue();
    206   key_dict->SetString("default",  "Ctrl+Shift+D");
    207   key_dict->SetString("windows",  "Ctrl+Shift+W");
    208   key_dict->SetString("mac",      "Ctrl+Shift+M");
    209   key_dict->SetString("linux",    "Ctrl+Shift+L");
    210   key_dict->SetString("chromeos", "Ctrl+Shift+C");
    211   input->Set("suggested_key", key_dict);
    212   input->SetString("description", description);
    213 
    214   extensions::Command command;
    215   base::string16 error;
    216   EXPECT_TRUE(command.Parse(input.get(), command_name, 0, &error));
    217   EXPECT_STREQ(description.c_str(),
    218                base::UTF16ToASCII(command.description()).c_str());
    219   EXPECT_STREQ(command_name.c_str(), command.command_name().c_str());
    220 
    221 #if defined(OS_WIN)
    222   ui::Accelerator accelerator(ui::VKEY_W,
    223                               ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
    224 #elif defined(OS_MACOSX)
    225   ui::Accelerator accelerator(ui::VKEY_M,
    226                               ui::EF_SHIFT_DOWN | ui::EF_COMMAND_DOWN);
    227 #elif defined(OS_CHROMEOS)
    228   ui::Accelerator accelerator(ui::VKEY_C,
    229                               ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
    230 #elif defined(OS_LINUX)
    231   ui::Accelerator accelerator(ui::VKEY_L,
    232                               ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
    233 #else
    234   ui::Accelerator accelerator(ui::VKEY_D,
    235                               ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
    236 #endif
    237   EXPECT_EQ(accelerator, command.accelerator());
    238 
    239   // Misspell a platform.
    240   key_dict->SetString("windosw", "Ctrl+M");
    241   EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
    242   EXPECT_TRUE(key_dict->Remove("windosw", NULL));
    243 
    244   // Now remove platform specific keys (leaving just "default") and make sure
    245   // every platform falls back to the default.
    246   EXPECT_TRUE(key_dict->Remove("windows", NULL));
    247   EXPECT_TRUE(key_dict->Remove("mac", NULL));
    248   EXPECT_TRUE(key_dict->Remove("linux", NULL));
    249   EXPECT_TRUE(key_dict->Remove("chromeos", NULL));
    250   EXPECT_TRUE(command.Parse(input.get(), command_name, 0, &error));
    251   EXPECT_EQ(ui::VKEY_D, command.accelerator().key_code());
    252 
    253   // Now remove "default", leaving no option but failure. Or, in the words of
    254   // the immortal Adam Savage: "Failure is always an option".
    255   EXPECT_TRUE(key_dict->Remove("default", NULL));
    256   EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
    257 
    258   // Make sure Command is not supported for non-Mac platforms.
    259   key_dict->SetString("default", "Command+M");
    260   EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
    261   EXPECT_TRUE(key_dict->Remove("default", NULL));
    262   key_dict->SetString("windows", "Command+M");
    263   EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
    264   EXPECT_TRUE(key_dict->Remove("windows", NULL));
    265 
    266   // Now add only a valid platform that we are not running on to make sure devs
    267   // are notified of errors on other platforms.
    268 #if defined(OS_WIN)
    269   key_dict->SetString("mac", "Ctrl+Shift+M");
    270 #else
    271   key_dict->SetString("windows", "Ctrl+Shift+W");
    272 #endif
    273   EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
    274 
    275   // Make sure Mac specific keys are not processed on other platforms.
    276 #if !defined(OS_MACOSX)
    277   key_dict->SetString("windows", "Command+Shift+M");
    278   EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
    279 #endif
    280 }
    281 
    282 TEST(CommandTest, ExtensionCommandParsingPlatformSpecific) {
    283   ui::Accelerator search_a(ui::VKEY_A, ui::EF_COMMAND_DOWN);
    284   ui::Accelerator search_shift_z(ui::VKEY_Z,
    285                                  ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN);
    286 
    287   ConstCommandsTestData kChromeOsTests[] = {
    288       {true, search_shift_z, "command", "Search+Shift+Z", "description"},
    289       {true, search_a, "command", "Search+A", "description"},
    290       // Command is not valid on Chrome OS.
    291       {false, search_shift_z, "command", "Command+Shift+Z", "description"},
    292   };
    293 
    294   std::vector<std::string> chromeos;
    295   chromeos.push_back("chromeos");
    296   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kChromeOsTests); ++i)
    297     CheckParse(kChromeOsTests[i], i, true, chromeos);
    298 
    299   ConstCommandsTestData kNonChromeOsSearchTests[] = {
    300       {false, search_shift_z, "command", "Search+Shift+Z", "description"},
    301   };
    302   std::vector<std::string> non_chromeos;
    303   non_chromeos.push_back("default");
    304   non_chromeos.push_back("windows");
    305   non_chromeos.push_back("mac");
    306   non_chromeos.push_back("linux");
    307 
    308   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kNonChromeOsSearchTests); ++i)
    309     CheckParse(kNonChromeOsSearchTests[i], i, true, non_chromeos);
    310 }
    311