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