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