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 <vector> 6 7 #include "base/json/json_reader.h" 8 #include "base/memory/scoped_temp_dir.h" 9 #include "base/memory/scoped_vector.h" 10 #include "base/path_service.h" 11 #include "base/utf_string_conversions.h" 12 #include "base/values.h" 13 #include "chrome/browser/extensions/extension_event_router.h" 14 #include "chrome/browser/extensions/extension_menu_manager.h" 15 #include "chrome/browser/extensions/test_extension_prefs.h" 16 #include "chrome/common/chrome_paths.h" 17 #include "chrome/common/extensions/extension.h" 18 #include "chrome/common/extensions/extension_constants.h" 19 #include "chrome/test/testing_profile.h" 20 #include "content/browser/browser_thread.h" 21 #include "content/common/notification_service.h" 22 #include "testing/gmock/include/gmock/gmock.h" 23 #include "testing/gtest/include/gtest/gtest.h" 24 #include "webkit/glue/context_menu.h" 25 26 using testing::_; 27 using testing::AtLeast; 28 using testing::Return; 29 using testing::SaveArg; 30 31 // Base class for tests. 32 class ExtensionMenuManagerTest : public testing::Test { 33 public: 34 ExtensionMenuManagerTest() 35 : ui_thread_(BrowserThread::UI, &message_loop_), 36 file_thread_(BrowserThread::FILE, &message_loop_), 37 next_id_(1) { 38 } 39 40 // Returns a test item. 41 ExtensionMenuItem* CreateTestItem(Extension* extension) { 42 ExtensionMenuItem::Type type = ExtensionMenuItem::NORMAL; 43 ExtensionMenuItem::ContextList contexts(ExtensionMenuItem::ALL); 44 ExtensionMenuItem::Id id(NULL, extension->id(), next_id_++); 45 return new ExtensionMenuItem(id, "test", false, type, contexts); 46 } 47 48 // Creates and returns a test Extension. The caller does *not* own the return 49 // value. 50 Extension* AddExtension(std::string name) { 51 scoped_refptr<Extension> extension = prefs_.AddExtension(name); 52 extensions_.push_back(extension); 53 return extension; 54 } 55 56 protected: 57 MessageLoopForUI message_loop_; 58 BrowserThread ui_thread_; 59 BrowserThread file_thread_; 60 61 ExtensionMenuManager manager_; 62 ExtensionList extensions_; 63 TestExtensionPrefs prefs_; 64 int next_id_; 65 66 private: 67 DISALLOW_COPY_AND_ASSIGN(ExtensionMenuManagerTest); 68 }; 69 70 // Tests adding, getting, and removing items. 71 TEST_F(ExtensionMenuManagerTest, AddGetRemoveItems) { 72 Extension* extension = AddExtension("test"); 73 74 // Add a new item, make sure you can get it back. 75 ExtensionMenuItem* item1 = CreateTestItem(extension); 76 ASSERT_TRUE(item1 != NULL); 77 ASSERT_TRUE(manager_.AddContextItem(extension, item1)); 78 ASSERT_EQ(item1, manager_.GetItemById(item1->id())); 79 const ExtensionMenuItem::List* items = 80 manager_.MenuItems(item1->extension_id()); 81 ASSERT_EQ(1u, items->size()); 82 ASSERT_EQ(item1, items->at(0)); 83 84 // Add a second item, make sure it comes back too. 85 ExtensionMenuItem* item2 = CreateTestItem(extension); 86 ASSERT_TRUE(manager_.AddContextItem(extension, item2)); 87 ASSERT_EQ(item2, manager_.GetItemById(item2->id())); 88 items = manager_.MenuItems(item2->extension_id()); 89 ASSERT_EQ(2u, items->size()); 90 ASSERT_EQ(item1, items->at(0)); 91 ASSERT_EQ(item2, items->at(1)); 92 93 // Try adding item 3, then removing it. 94 ExtensionMenuItem* item3 = CreateTestItem(extension); 95 ExtensionMenuItem::Id id3 = item3->id(); 96 std::string extension_id = item3->extension_id(); 97 ASSERT_TRUE(manager_.AddContextItem(extension, item3)); 98 ASSERT_EQ(item3, manager_.GetItemById(id3)); 99 ASSERT_EQ(3u, manager_.MenuItems(extension_id)->size()); 100 ASSERT_TRUE(manager_.RemoveContextMenuItem(id3)); 101 ASSERT_EQ(NULL, manager_.GetItemById(id3)); 102 ASSERT_EQ(2u, manager_.MenuItems(extension_id)->size()); 103 104 // Make sure removing a non-existent item returns false. 105 ExtensionMenuItem::Id id(NULL, extension->id(), id3.uid + 50); 106 ASSERT_FALSE(manager_.RemoveContextMenuItem(id)); 107 } 108 109 // Test adding/removing child items. 110 TEST_F(ExtensionMenuManagerTest, ChildFunctions) { 111 Extension* extension1 = AddExtension("1111"); 112 Extension* extension2 = AddExtension("2222"); 113 Extension* extension3 = AddExtension("3333"); 114 115 ExtensionMenuItem* item1 = CreateTestItem(extension1); 116 ExtensionMenuItem* item2 = CreateTestItem(extension2); 117 ExtensionMenuItem* item2_child = CreateTestItem(extension2); 118 ExtensionMenuItem* item2_grandchild = CreateTestItem(extension2); 119 120 // This third item we expect to fail inserting, so we use a scoped_ptr to make 121 // sure it gets deleted. 122 scoped_ptr<ExtensionMenuItem> item3(CreateTestItem(extension3)); 123 124 // Add in the first two items. 125 ASSERT_TRUE(manager_.AddContextItem(extension1, item1)); 126 ASSERT_TRUE(manager_.AddContextItem(extension2, item2)); 127 128 ExtensionMenuItem::Id id1 = item1->id(); 129 ExtensionMenuItem::Id id2 = item2->id(); 130 131 // Try adding item3 as a child of item2 - this should fail because item3 has 132 // a different extension id. 133 ASSERT_FALSE(manager_.AddChildItem(id2, item3.get())); 134 135 // Add item2_child as a child of item2. 136 ExtensionMenuItem::Id id2_child = item2_child->id(); 137 ASSERT_TRUE(manager_.AddChildItem(id2, item2_child)); 138 ASSERT_EQ(1, item2->child_count()); 139 ASSERT_EQ(0, item1->child_count()); 140 ASSERT_EQ(item2_child, manager_.GetItemById(id2_child)); 141 142 ASSERT_EQ(1u, manager_.MenuItems(item1->extension_id())->size()); 143 ASSERT_EQ(item1, manager_.MenuItems(item1->extension_id())->at(0)); 144 145 // Add item2_grandchild as a child of item2_child, then remove it. 146 ExtensionMenuItem::Id id2_grandchild = item2_grandchild->id(); 147 ASSERT_TRUE(manager_.AddChildItem(id2_child, item2_grandchild)); 148 ASSERT_EQ(1, item2->child_count()); 149 ASSERT_EQ(1, item2_child->child_count()); 150 ASSERT_TRUE(manager_.RemoveContextMenuItem(id2_grandchild)); 151 152 // We should only get 1 thing back when asking for item2's extension id, since 153 // it has a child item. 154 ASSERT_EQ(1u, manager_.MenuItems(item2->extension_id())->size()); 155 ASSERT_EQ(item2, manager_.MenuItems(item2->extension_id())->at(0)); 156 157 // Remove child2_item. 158 ASSERT_TRUE(manager_.RemoveContextMenuItem(id2_child)); 159 ASSERT_EQ(1u, manager_.MenuItems(item2->extension_id())->size()); 160 ASSERT_EQ(item2, manager_.MenuItems(item2->extension_id())->at(0)); 161 ASSERT_EQ(0, item2->child_count()); 162 } 163 164 // Tests that deleting a parent properly removes descendants. 165 TEST_F(ExtensionMenuManagerTest, DeleteParent) { 166 Extension* extension = AddExtension("1111"); 167 168 // Set up 5 items to add. 169 ExtensionMenuItem* item1 = CreateTestItem(extension); 170 ExtensionMenuItem* item2 = CreateTestItem(extension); 171 ExtensionMenuItem* item3 = CreateTestItem(extension); 172 ExtensionMenuItem* item4 = CreateTestItem(extension); 173 ExtensionMenuItem* item5 = CreateTestItem(extension); 174 ExtensionMenuItem* item6 = CreateTestItem(extension); 175 ExtensionMenuItem::Id item1_id = item1->id(); 176 ExtensionMenuItem::Id item2_id = item2->id(); 177 ExtensionMenuItem::Id item3_id = item3->id(); 178 ExtensionMenuItem::Id item4_id = item4->id(); 179 ExtensionMenuItem::Id item5_id = item5->id(); 180 ExtensionMenuItem::Id item6_id = item6->id(); 181 182 // Add the items in the hierarchy 183 // item1 -> item2 -> item3 -> item4 -> item5 -> item6. 184 ASSERT_TRUE(manager_.AddContextItem(extension, item1)); 185 ASSERT_TRUE(manager_.AddChildItem(item1_id, item2)); 186 ASSERT_TRUE(manager_.AddChildItem(item2_id, item3)); 187 ASSERT_TRUE(manager_.AddChildItem(item3_id, item4)); 188 ASSERT_TRUE(manager_.AddChildItem(item4_id, item5)); 189 ASSERT_TRUE(manager_.AddChildItem(item5_id, item6)); 190 ASSERT_EQ(item1, manager_.GetItemById(item1_id)); 191 ASSERT_EQ(item2, manager_.GetItemById(item2_id)); 192 ASSERT_EQ(item3, manager_.GetItemById(item3_id)); 193 ASSERT_EQ(item4, manager_.GetItemById(item4_id)); 194 ASSERT_EQ(item5, manager_.GetItemById(item5_id)); 195 ASSERT_EQ(item6, manager_.GetItemById(item6_id)); 196 ASSERT_EQ(1u, manager_.MenuItems(extension->id())->size()); 197 ASSERT_EQ(6u, manager_.items_by_id_.size()); 198 199 // Remove item6 (a leaf node). 200 ASSERT_TRUE(manager_.RemoveContextMenuItem(item6_id)); 201 ASSERT_EQ(item1, manager_.GetItemById(item1_id)); 202 ASSERT_EQ(item2, manager_.GetItemById(item2_id)); 203 ASSERT_EQ(item3, manager_.GetItemById(item3_id)); 204 ASSERT_EQ(item4, manager_.GetItemById(item4_id)); 205 ASSERT_EQ(item5, manager_.GetItemById(item5_id)); 206 ASSERT_EQ(NULL, manager_.GetItemById(item6_id)); 207 ASSERT_EQ(1u, manager_.MenuItems(extension->id())->size()); 208 ASSERT_EQ(5u, manager_.items_by_id_.size()); 209 210 // Remove item4 and make sure item5 is gone as well. 211 ASSERT_TRUE(manager_.RemoveContextMenuItem(item4_id)); 212 ASSERT_EQ(item1, manager_.GetItemById(item1_id)); 213 ASSERT_EQ(item2, manager_.GetItemById(item2_id)); 214 ASSERT_EQ(item3, manager_.GetItemById(item3_id)); 215 ASSERT_EQ(NULL, manager_.GetItemById(item4_id)); 216 ASSERT_EQ(NULL, manager_.GetItemById(item5_id)); 217 ASSERT_EQ(1u, manager_.MenuItems(extension->id())->size()); 218 ASSERT_EQ(3u, manager_.items_by_id_.size()); 219 220 // Now remove item1 and make sure item2 and item3 are gone as well. 221 ASSERT_TRUE(manager_.RemoveContextMenuItem(item1_id)); 222 ASSERT_EQ(NULL, manager_.MenuItems(extension->id())); 223 ASSERT_EQ(0u, manager_.items_by_id_.size()); 224 ASSERT_EQ(NULL, manager_.GetItemById(item1_id)); 225 ASSERT_EQ(NULL, manager_.GetItemById(item2_id)); 226 ASSERT_EQ(NULL, manager_.GetItemById(item3_id)); 227 } 228 229 // Tests changing parents. 230 TEST_F(ExtensionMenuManagerTest, ChangeParent) { 231 Extension* extension1 = AddExtension("1111"); 232 233 // First create two items and add them both to the manager. 234 ExtensionMenuItem* item1 = CreateTestItem(extension1); 235 ExtensionMenuItem* item2 = CreateTestItem(extension1); 236 237 ASSERT_TRUE(manager_.AddContextItem(extension1, item1)); 238 ASSERT_TRUE(manager_.AddContextItem(extension1, item2)); 239 240 const ExtensionMenuItem::List* items = 241 manager_.MenuItems(item1->extension_id()); 242 ASSERT_EQ(2u, items->size()); 243 ASSERT_EQ(item1, items->at(0)); 244 ASSERT_EQ(item2, items->at(1)); 245 246 // Now create a third item, initially add it as a child of item1, then move 247 // it to be a child of item2. 248 ExtensionMenuItem* item3 = CreateTestItem(extension1); 249 250 ASSERT_TRUE(manager_.AddChildItem(item1->id(), item3)); 251 ASSERT_EQ(1, item1->child_count()); 252 ASSERT_EQ(item3, item1->children()[0]); 253 254 ASSERT_TRUE(manager_.ChangeParent(item3->id(), &item2->id())); 255 ASSERT_EQ(0, item1->child_count()); 256 ASSERT_EQ(1, item2->child_count()); 257 ASSERT_EQ(item3, item2->children()[0]); 258 259 // Move item2 to be a child of item1. 260 ASSERT_TRUE(manager_.ChangeParent(item2->id(), &item1->id())); 261 ASSERT_EQ(1, item1->child_count()); 262 ASSERT_EQ(item2, item1->children()[0]); 263 ASSERT_EQ(1, item2->child_count()); 264 ASSERT_EQ(item3, item2->children()[0]); 265 266 // Since item2 was a top-level item but is no longer, we should only have 1 267 // top-level item. 268 items = manager_.MenuItems(item1->extension_id()); 269 ASSERT_EQ(1u, items->size()); 270 ASSERT_EQ(item1, items->at(0)); 271 272 // Move item3 back to being a child of item1, so it's now a sibling of item2. 273 ASSERT_TRUE(manager_.ChangeParent(item3->id(), &item1->id())); 274 ASSERT_EQ(2, item1->child_count()); 275 ASSERT_EQ(item2, item1->children()[0]); 276 ASSERT_EQ(item3, item1->children()[1]); 277 278 // Try switching item3 to be the parent of item1 - this should fail. 279 ASSERT_FALSE(manager_.ChangeParent(item1->id(), &item3->id())); 280 ASSERT_EQ(0, item3->child_count()); 281 ASSERT_EQ(2, item1->child_count()); 282 ASSERT_EQ(item2, item1->children()[0]); 283 ASSERT_EQ(item3, item1->children()[1]); 284 items = manager_.MenuItems(item1->extension_id()); 285 ASSERT_EQ(1u, items->size()); 286 ASSERT_EQ(item1, items->at(0)); 287 288 // Move item2 to be a top-level item. 289 ASSERT_TRUE(manager_.ChangeParent(item2->id(), NULL)); 290 items = manager_.MenuItems(item1->extension_id()); 291 ASSERT_EQ(2u, items->size()); 292 ASSERT_EQ(item1, items->at(0)); 293 ASSERT_EQ(item2, items->at(1)); 294 ASSERT_EQ(1, item1->child_count()); 295 ASSERT_EQ(item3, item1->children()[0]); 296 297 // Make sure you can't move a node to be a child of another extension's item. 298 Extension* extension2 = AddExtension("2222"); 299 ExtensionMenuItem* item4 = CreateTestItem(extension2); 300 ASSERT_TRUE(manager_.AddContextItem(extension2, item4)); 301 ASSERT_FALSE(manager_.ChangeParent(item4->id(), &item1->id())); 302 ASSERT_FALSE(manager_.ChangeParent(item1->id(), &item4->id())); 303 304 // Make sure you can't make an item be it's own parent. 305 ASSERT_FALSE(manager_.ChangeParent(item1->id(), &item1->id())); 306 } 307 308 // Tests that we properly remove an extension's menu item when that extension is 309 // unloaded. 310 TEST_F(ExtensionMenuManagerTest, ExtensionUnloadRemovesMenuItems) { 311 NotificationService* notifier = NotificationService::current(); 312 ASSERT_TRUE(notifier != NULL); 313 314 // Create a test extension. 315 Extension* extension1 = AddExtension("1111"); 316 317 // Create an ExtensionMenuItem and put it into the manager. 318 ExtensionMenuItem* item1 = CreateTestItem(extension1); 319 ExtensionMenuItem::Id id1 = item1->id(); 320 ASSERT_EQ(extension1->id(), item1->extension_id()); 321 ASSERT_TRUE(manager_.AddContextItem(extension1, item1)); 322 ASSERT_EQ(1u, manager_.MenuItems(extension1->id())->size()); 323 324 // Create a menu item with a different extension id and add it to the manager. 325 Extension* extension2 = AddExtension("2222"); 326 ExtensionMenuItem* item2 = CreateTestItem(extension2); 327 ASSERT_NE(item1->extension_id(), item2->extension_id()); 328 ASSERT_TRUE(manager_.AddContextItem(extension2, item2)); 329 330 // Notify that the extension was unloaded, and make sure the right item is 331 // gone. 332 UnloadedExtensionInfo details(extension1, UnloadedExtensionInfo::DISABLE); 333 notifier->Notify(NotificationType::EXTENSION_UNLOADED, 334 Source<Profile>(NULL), 335 Details<UnloadedExtensionInfo>(&details)); 336 ASSERT_EQ(NULL, manager_.MenuItems(extension1->id())); 337 ASSERT_EQ(1u, manager_.MenuItems(extension2->id())->size()); 338 ASSERT_TRUE(manager_.GetItemById(id1) == NULL); 339 ASSERT_TRUE(manager_.GetItemById(item2->id()) != NULL); 340 } 341 342 // A mock message service for tests of ExtensionMenuManager::ExecuteCommand. 343 class MockExtensionEventRouter : public ExtensionEventRouter { 344 public: 345 explicit MockExtensionEventRouter(Profile* profile) : 346 ExtensionEventRouter(profile) {} 347 348 MOCK_METHOD5(DispatchEventImpl, void(const std::string& extension_id, 349 const std::string& event_name, 350 const std::string& event_args, 351 Profile* source_profile, 352 const GURL& event_url)); 353 354 private: 355 DISALLOW_COPY_AND_ASSIGN(MockExtensionEventRouter); 356 }; 357 358 // A mock profile for tests of ExtensionMenuManager::ExecuteCommand. 359 class MockTestingProfile : public TestingProfile { 360 public: 361 MockTestingProfile() {} 362 MOCK_METHOD0(GetExtensionEventRouter, ExtensionEventRouter*()); 363 MOCK_METHOD0(IsOffTheRecord, bool()); 364 365 private: 366 DISALLOW_COPY_AND_ASSIGN(MockTestingProfile); 367 }; 368 369 // Tests the RemoveAll functionality. 370 TEST_F(ExtensionMenuManagerTest, RemoveAll) { 371 // Try removing all items for an extension id that doesn't have any items. 372 manager_.RemoveAllContextItems("CCCC"); 373 374 // Add 2 top-level and one child item for extension 1. 375 Extension* extension1 = AddExtension("1111"); 376 ExtensionMenuItem* item1 = CreateTestItem(extension1); 377 ExtensionMenuItem* item2 = CreateTestItem(extension1); 378 ExtensionMenuItem* item3 = CreateTestItem(extension1); 379 ASSERT_TRUE(manager_.AddContextItem(extension1, item1)); 380 ASSERT_TRUE(manager_.AddContextItem(extension1, item2)); 381 ASSERT_TRUE(manager_.AddChildItem(item1->id(), item3)); 382 383 // Add one top-level item for extension 2. 384 Extension* extension2 = AddExtension("2222"); 385 ExtensionMenuItem* item4 = CreateTestItem(extension2); 386 ASSERT_TRUE(manager_.AddContextItem(extension2, item4)); 387 388 EXPECT_EQ(2u, manager_.MenuItems(extension1->id())->size()); 389 EXPECT_EQ(1u, manager_.MenuItems(extension2->id())->size()); 390 391 // Remove extension2's item. 392 manager_.RemoveAllContextItems(extension2->id()); 393 EXPECT_EQ(2u, manager_.MenuItems(extension1->id())->size()); 394 EXPECT_EQ(NULL, manager_.MenuItems(extension2->id())); 395 396 // Remove extension1's items. 397 manager_.RemoveAllContextItems(extension1->id()); 398 EXPECT_EQ(NULL, manager_.MenuItems(extension1->id())); 399 } 400 401 // Tests that removing all items one-by-one doesn't leave an entry around. 402 TEST_F(ExtensionMenuManagerTest, RemoveOneByOne) { 403 // Add 2 test items. 404 Extension* extension1 = AddExtension("1111"); 405 ExtensionMenuItem* item1 = CreateTestItem(extension1); 406 ExtensionMenuItem* item2 = CreateTestItem(extension1); 407 ASSERT_TRUE(manager_.AddContextItem(extension1, item1)); 408 ASSERT_TRUE(manager_.AddContextItem(extension1, item2)); 409 410 ASSERT_FALSE(manager_.context_items_.empty()); 411 412 manager_.RemoveContextMenuItem(item1->id()); 413 manager_.RemoveContextMenuItem(item2->id()); 414 415 ASSERT_TRUE(manager_.context_items_.empty()); 416 } 417 418 TEST_F(ExtensionMenuManagerTest, ExecuteCommand) { 419 MockTestingProfile profile; 420 421 scoped_ptr<MockExtensionEventRouter> mock_event_router( 422 new MockExtensionEventRouter(&profile)); 423 424 ContextMenuParams params; 425 params.media_type = WebKit::WebContextMenuData::MediaTypeImage; 426 params.src_url = GURL("http://foo.bar/image.png"); 427 params.page_url = GURL("http://foo.bar"); 428 params.selection_text = ASCIIToUTF16("Hello World"); 429 params.is_editable = false; 430 431 Extension* extension = AddExtension("test"); 432 ExtensionMenuItem* item = CreateTestItem(extension); 433 ExtensionMenuItem::Id id = item->id(); 434 ASSERT_TRUE(manager_.AddContextItem(extension, item)); 435 436 EXPECT_CALL(profile, GetExtensionEventRouter()) 437 .Times(1) 438 .WillOnce(Return(mock_event_router.get())); 439 440 // Use the magic of googlemock to save a parameter to our mock's 441 // DispatchEventImpl method into event_args. 442 std::string event_args; 443 std::string expected_event_name = "contextMenus"; 444 EXPECT_CALL(*mock_event_router.get(), 445 DispatchEventImpl(item->extension_id(), 446 expected_event_name, 447 _, 448 &profile, 449 GURL())) 450 .Times(1) 451 .WillOnce(SaveArg<2>(&event_args)); 452 453 manager_.ExecuteCommand(&profile, NULL /* tab_contents */, params, id); 454 455 // Parse the json event_args, which should turn into a 2-element list where 456 // the first element is a dictionary we want to inspect for the correct 457 // values. 458 scoped_ptr<Value> result(base::JSONReader::Read(event_args, true)); 459 Value* value = result.get(); 460 ASSERT_TRUE(result.get() != NULL); 461 ASSERT_EQ(Value::TYPE_LIST, value->GetType()); 462 ListValue* list = static_cast<ListValue*>(value); 463 ASSERT_EQ(2u, list->GetSize()); 464 465 DictionaryValue* info; 466 ASSERT_TRUE(list->GetDictionary(0, &info)); 467 468 int tmp_id = 0; 469 ASSERT_TRUE(info->GetInteger("menuItemId", &tmp_id)); 470 ASSERT_EQ(id.uid, tmp_id); 471 472 std::string tmp; 473 ASSERT_TRUE(info->GetString("mediaType", &tmp)); 474 ASSERT_EQ("image", tmp); 475 ASSERT_TRUE(info->GetString("srcUrl", &tmp)); 476 ASSERT_EQ(params.src_url.spec(), tmp); 477 ASSERT_TRUE(info->GetString("pageUrl", &tmp)); 478 ASSERT_EQ(params.page_url.spec(), tmp); 479 480 string16 tmp16; 481 ASSERT_TRUE(info->GetString("selectionText", &tmp16)); 482 ASSERT_EQ(params.selection_text, tmp16); 483 484 bool bool_tmp = true; 485 ASSERT_TRUE(info->GetBoolean("editable", &bool_tmp)); 486 ASSERT_EQ(params.is_editable, bool_tmp); 487 } 488