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 TEST(CommandTest, ExtensionCommandParsing) {
     18   const ui::Accelerator none = ui::Accelerator();
     19   const ui::Accelerator shift_f = ui::Accelerator(ui::VKEY_F,
     20                                                   ui::EF_SHIFT_DOWN);
     21 #if defined(OS_MACOSX)
     22   int ctrl = ui::EF_COMMAND_DOWN;
     23 #else
     24   int ctrl = ui::EF_CONTROL_DOWN;
     25 #endif
     26 
     27   const ui::Accelerator ctrl_f = ui::Accelerator(ui::VKEY_F, ctrl);
     28   const ui::Accelerator alt_f = ui::Accelerator(ui::VKEY_F, ui::EF_ALT_DOWN);
     29   const ui::Accelerator ctrl_shift_f =
     30       ui::Accelerator(ui::VKEY_F, ctrl | ui::EF_SHIFT_DOWN);
     31   const ui::Accelerator alt_shift_f =
     32       ui::Accelerator(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN);
     33   const ui::Accelerator ctrl_1 = ui::Accelerator(ui::VKEY_1, ctrl);
     34   const ui::Accelerator ctrl_comma = ui::Accelerator(ui::VKEY_OEM_COMMA, ctrl);
     35   const ui::Accelerator ctrl_dot = ui::Accelerator(ui::VKEY_OEM_PERIOD, ctrl);
     36   const ui::Accelerator ctrl_left = ui::Accelerator(ui::VKEY_LEFT, ctrl);
     37   const ui::Accelerator ctrl_right = ui::Accelerator(ui::VKEY_RIGHT, ctrl);
     38   const ui::Accelerator ctrl_up = ui::Accelerator(ui::VKEY_UP, ctrl);
     39   const ui::Accelerator ctrl_down = ui::Accelerator(ui::VKEY_DOWN, ctrl);
     40   const ui::Accelerator ctrl_ins = ui::Accelerator(ui::VKEY_INSERT, ctrl);
     41   const ui::Accelerator ctrl_del = ui::Accelerator(ui::VKEY_DELETE, ctrl);
     42   const ui::Accelerator ctrl_home = ui::Accelerator(ui::VKEY_HOME, ctrl);
     43   const ui::Accelerator ctrl_end = ui::Accelerator(ui::VKEY_END, ctrl);
     44   const ui::Accelerator ctrl_pgup = ui::Accelerator(ui::VKEY_PRIOR, ctrl);
     45   const ui::Accelerator ctrl_pgdwn = ui::Accelerator(ui::VKEY_NEXT, ctrl);
     46   const ui::Accelerator next_track =
     47       ui::Accelerator(ui::VKEY_MEDIA_NEXT_TRACK, ui::EF_NONE);
     48   const ui::Accelerator prev_track =
     49       ui::Accelerator(ui::VKEY_MEDIA_PREV_TRACK, ui::EF_NONE);
     50   const ui::Accelerator play_pause =
     51       ui::Accelerator(ui::VKEY_MEDIA_PLAY_PAUSE, ui::EF_NONE);
     52   const ui::Accelerator stop =
     53       ui::Accelerator(ui::VKEY_MEDIA_STOP, ui::EF_NONE);
     54 
     55   const struct {
     56     bool expected_result;
     57     ui::Accelerator accelerator;
     58     const char* command_name;
     59     const char* key;
     60     const char* description;
     61   } kTests[] = {
     62     // Negative test (one or more missing required fields). We don't need to
     63     // test |command_name| being blank as it is used as a key in the manifest,
     64     // so it can't be blank (and we CHECK() when it is). A blank shortcut is
     65     // permitted.
     66     { false, none, "command", "",       "" },
     67     { false, none, "command", "Ctrl+f", "" },
     68     // Ctrl+Alt is not permitted, see MSDN link in comments in Parse function.
     69     { false, none, "command", "Ctrl+Alt+F", "description" },
     70     // Unsupported shortcuts/too many, or missing modifier.
     71     { false, none, "command", "A",                "description" },
     72     { false, none, "command", "F10",              "description" },
     73     { false, none, "command", "Ctrl+F+G",         "description" },
     74     { false, none, "command", "Ctrl+Alt+Shift+G", "description" },
     75     // Shift on its own is not supported.
     76     { false, shift_f, "command", "Shift+F",       "description" },
     77     { false, shift_f, "command", "F+Shift",       "description" },
     78     // Basic tests.
     79     { true, none,         "command", "",             "description" },
     80     { true, ctrl_f,       "command", "Ctrl+F",       "description" },
     81     { true, alt_f,        "command", "Alt+F",        "description" },
     82     { true, ctrl_shift_f, "command", "Ctrl+Shift+F", "description" },
     83     { true, alt_shift_f,  "command", "Alt+Shift+F",  "description" },
     84     { true, ctrl_1,       "command", "Ctrl+1",       "description" },
     85     // Shortcut token order tests.
     86     { true, ctrl_f,       "command", "F+Ctrl",       "description" },
     87     { true, alt_f,        "command", "F+Alt",        "description" },
     88     { true, ctrl_shift_f, "command", "F+Ctrl+Shift", "description" },
     89     { true, ctrl_shift_f, "command", "F+Shift+Ctrl", "description" },
     90     { true, alt_shift_f,  "command", "F+Alt+Shift",  "description" },
     91     { true, alt_shift_f,  "command", "F+Shift+Alt",  "description" },
     92     // Case insensitivity is not OK.
     93     { false, ctrl_f, "command", "Ctrl+f", "description" },
     94     { false, ctrl_f, "command", "cTrL+F", "description" },
     95     // Skipping description is OK for browser- and pageActions.
     96     { true, ctrl_f, "_execute_browser_action", "Ctrl+F", "" },
     97     { true, ctrl_f, "_execute_page_action",    "Ctrl+F", "" },
     98     // Home, End, Arrow keys, etc.
     99     { true, ctrl_comma, "_execute_browser_action", "Ctrl+Comma", "" },
    100     { true, ctrl_dot, "_execute_browser_action", "Ctrl+Period", "" },
    101     { true, ctrl_left, "_execute_browser_action", "Ctrl+Left", "" },
    102     { true, ctrl_right, "_execute_browser_action", "Ctrl+Right", "" },
    103     { true, ctrl_up, "_execute_browser_action", "Ctrl+Up", "" },
    104     { true, ctrl_down, "_execute_browser_action", "Ctrl+Down", "" },
    105     { true, ctrl_ins, "_execute_browser_action", "Ctrl+Insert", "" },
    106     { true, ctrl_del, "_execute_browser_action", "Ctrl+Delete", "" },
    107     { true, ctrl_home, "_execute_browser_action", "Ctrl+Home", "" },
    108     { true, ctrl_end, "_execute_browser_action", "Ctrl+End", "" },
    109     { true, ctrl_pgup, "_execute_browser_action", "Ctrl+PageUp", "" },
    110     { true, ctrl_pgdwn, "_execute_browser_action", "Ctrl+PageDown", "" },
    111     // Media keys.
    112     { true, next_track, "command", "MediaNextTrack", "description" },
    113     { true, play_pause, "command", "MediaPlayPause", "description" },
    114     { true, prev_track, "command", "MediaPrevTrack", "description" },
    115     { true, stop, "command", "MediaStop", "description" },
    116     { false, none, "_execute_browser_action", "MediaNextTrack", "" },
    117     { false, none, "_execute_page_action", "MediaPrevTrack", "" },
    118     { false, none, "command", "Ctrl+Shift+MediaPrevTrack", "description" },
    119   };
    120 
    121   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) {
    122     // First parse the command as a simple string.
    123     scoped_ptr<base::DictionaryValue> input(new base::DictionaryValue);
    124     input->SetString("suggested_key", kTests[i].key);
    125     input->SetString("description", kTests[i].description);
    126 
    127     SCOPED_TRACE(std::string("Command name: |") + kTests[i].command_name +
    128                  "| key: |" + kTests[i].key +
    129                  "| description: |" + kTests[i].description +
    130                  "| index: " + base::IntToString(i));
    131 
    132     extensions::Command command;
    133     base::string16 error;
    134     bool result =
    135         command.Parse(input.get(), kTests[i].command_name, i, &error);
    136 
    137     EXPECT_EQ(kTests[i].expected_result, result);
    138     if (result) {
    139       EXPECT_STREQ(kTests[i].description,
    140                    UTF16ToASCII(command.description()).c_str());
    141       EXPECT_STREQ(kTests[i].command_name, command.command_name().c_str());
    142       EXPECT_EQ(kTests[i].accelerator, command.accelerator());
    143     }
    144 
    145     // Now parse the command as a dictionary of multiple values.
    146     if (kTests[i].key[0] != '\0') {
    147       input.reset(new base::DictionaryValue);
    148       base::DictionaryValue* key_dict = new base::DictionaryValue();
    149       key_dict->SetString("default", kTests[i].key);
    150       key_dict->SetString("windows", kTests[i].key);
    151       key_dict->SetString("mac", kTests[i].key);
    152       input->Set("suggested_key", key_dict);
    153       input->SetString("description", kTests[i].description);
    154 
    155       result = command.Parse(input.get(), kTests[i].command_name, i, &error);
    156 
    157       EXPECT_EQ(kTests[i].expected_result, result);
    158       if (result) {
    159         EXPECT_STREQ(kTests[i].description,
    160                      UTF16ToASCII(command.description()).c_str());
    161         EXPECT_STREQ(kTests[i].command_name, command.command_name().c_str());
    162         EXPECT_EQ(kTests[i].accelerator, command.accelerator());
    163       }
    164     }
    165   }
    166 }
    167 
    168 TEST(CommandTest, ExtensionCommandParsingFallback) {
    169   std::string description = "desc";
    170   std::string command_name = "foo";
    171 
    172   // Test that platform specific keys are honored on each platform, despite
    173   // fallback being given.
    174   scoped_ptr<base::DictionaryValue> input(new base::DictionaryValue);
    175   base::DictionaryValue* key_dict = new base::DictionaryValue();
    176   key_dict->SetString("default",  "Ctrl+Shift+D");
    177   key_dict->SetString("windows",  "Ctrl+Shift+W");
    178   key_dict->SetString("mac",      "Ctrl+Shift+M");
    179   key_dict->SetString("linux",    "Ctrl+Shift+L");
    180   key_dict->SetString("chromeos", "Ctrl+Shift+C");
    181   input->Set("suggested_key", key_dict);
    182   input->SetString("description", description);
    183 
    184   extensions::Command command;
    185   base::string16 error;
    186   EXPECT_TRUE(command.Parse(input.get(), command_name, 0, &error));
    187   EXPECT_STREQ(description.c_str(),
    188                UTF16ToASCII(command.description()).c_str());
    189   EXPECT_STREQ(command_name.c_str(), command.command_name().c_str());
    190 
    191 #if defined(OS_WIN)
    192   ui::Accelerator accelerator(ui::VKEY_W,
    193                               ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
    194 #elif defined(OS_MACOSX)
    195   ui::Accelerator accelerator(ui::VKEY_M,
    196                               ui::EF_SHIFT_DOWN | ui::EF_COMMAND_DOWN);
    197 #elif defined(OS_CHROMEOS)
    198   ui::Accelerator accelerator(ui::VKEY_C,
    199                               ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
    200 #elif defined(OS_LINUX)
    201   ui::Accelerator accelerator(ui::VKEY_L,
    202                               ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
    203 #else
    204   ui::Accelerator accelerator(ui::VKEY_D,
    205                               ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
    206 #endif
    207   EXPECT_EQ(accelerator, command.accelerator());
    208 
    209   // Misspell a platform.
    210   key_dict->SetString("windosw", "Ctrl+M");
    211   EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
    212   EXPECT_TRUE(key_dict->Remove("windosw", NULL));
    213 
    214   // Now remove platform specific keys (leaving just "default") and make sure
    215   // every platform falls back to the default.
    216   EXPECT_TRUE(key_dict->Remove("windows", NULL));
    217   EXPECT_TRUE(key_dict->Remove("mac", NULL));
    218   EXPECT_TRUE(key_dict->Remove("linux", NULL));
    219   EXPECT_TRUE(key_dict->Remove("chromeos", NULL));
    220   EXPECT_TRUE(command.Parse(input.get(), command_name, 0, &error));
    221   EXPECT_EQ(ui::VKEY_D, command.accelerator().key_code());
    222 
    223   // Now remove "default", leaving no option but failure. Or, in the words of
    224   // the immortal Adam Savage: "Failure is always an option".
    225   EXPECT_TRUE(key_dict->Remove("default", NULL));
    226   EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
    227 
    228   // Make sure Command is not supported for non-Mac platforms.
    229   key_dict->SetString("default", "Command+M");
    230   EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
    231   EXPECT_TRUE(key_dict->Remove("default", NULL));
    232   key_dict->SetString("windows", "Command+M");
    233   EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
    234   EXPECT_TRUE(key_dict->Remove("windows", NULL));
    235 
    236   // Now add only a valid platform that we are not running on to make sure devs
    237   // are notified of errors on other platforms.
    238 #if defined(OS_WIN)
    239   key_dict->SetString("mac", "Ctrl+Shift+M");
    240 #else
    241   key_dict->SetString("windows", "Ctrl+Shift+W");
    242 #endif
    243   EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
    244 
    245   // Make sure Mac specific keys are not processed on other platforms.
    246 #if !defined(OS_MACOSX)
    247   key_dict->SetString("windows", "Command+Shift+M");
    248   EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
    249 #endif
    250 }
    251