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 <vector> 6 7 #include "base/files/scoped_temp_dir.h" 8 #include "base/json/json_reader.h" 9 #include "base/memory/scoped_vector.h" 10 #include "base/message_loop/message_loop.h" 11 #include "base/path_service.h" 12 #include "base/prefs/pref_service.h" 13 #include "base/strings/utf_string_conversions.h" 14 #include "base/values.h" 15 #include "chrome/browser/chrome_notification_types.h" 16 #include "chrome/browser/extensions/event_names.h" 17 #include "chrome/browser/extensions/extension_system.h" 18 #include "chrome/browser/extensions/extension_system_factory.h" 19 #include "chrome/browser/extensions/menu_manager.h" 20 #include "chrome/browser/extensions/test_extension_prefs.h" 21 #include "chrome/browser/extensions/test_extension_system.h" 22 #include "chrome/browser/prefs/pref_service_syncable.h" 23 #include "chrome/common/chrome_paths.h" 24 #include "chrome/common/extensions/api/context_menus.h" 25 #include "chrome/test/base/testing_profile.h" 26 #include "content/public/browser/notification_service.h" 27 #include "content/public/common/context_menu_params.h" 28 #include "content/public/test/test_browser_thread.h" 29 #include "extensions/browser/event_router.h" 30 #include "extensions/common/extension.h" 31 #include "testing/gmock/include/gmock/gmock.h" 32 #include "testing/gtest/include/gtest/gtest.h" 33 34 using content::BrowserThread; 35 using testing::_; 36 using testing::AtLeast; 37 using testing::DeleteArg; 38 using testing::InSequence; 39 using testing::Return; 40 using testing::SaveArg; 41 42 namespace extensions { 43 44 namespace context_menus = api::context_menus; 45 46 // Base class for tests. 47 class MenuManagerTest : public testing::Test { 48 public: 49 MenuManagerTest() 50 : ui_thread_(BrowserThread::UI, &message_loop_), 51 file_thread_(BrowserThread::FILE, &message_loop_), 52 manager_(&profile_, ExtensionSystem::Get(&profile_)->state_store()), 53 prefs_(message_loop_.message_loop_proxy().get()), 54 next_id_(1) {} 55 56 virtual void TearDown() OVERRIDE { 57 prefs_.pref_service()->CommitPendingWrite(); 58 message_loop_.RunUntilIdle(); 59 } 60 61 // Returns a test item. 62 MenuItem* CreateTestItem(Extension* extension, bool incognito = false) { 63 MenuItem::Type type = MenuItem::NORMAL; 64 MenuItem::ContextList contexts(MenuItem::ALL); 65 MenuItem::Id id(incognito, extension->id()); 66 id.uid = next_id_++; 67 return new MenuItem(id, "test", false, true, type, contexts); 68 } 69 70 // Returns a test item with the given string ID. 71 MenuItem* CreateTestItemWithID(Extension* extension, 72 const std::string& string_id) { 73 MenuItem::Type type = MenuItem::NORMAL; 74 MenuItem::ContextList contexts(MenuItem::ALL); 75 MenuItem::Id id(false, extension->id()); 76 id.string_uid = string_id; 77 return new MenuItem(id, "test", false, true, type, contexts); 78 } 79 80 // Creates and returns a test Extension. The caller does *not* own the return 81 // value. 82 Extension* AddExtension(std::string name) { 83 scoped_refptr<Extension> extension = prefs_.AddExtension(name); 84 extensions_.push_back(extension); 85 return extension.get(); 86 } 87 88 protected: 89 base::MessageLoopForUI message_loop_; 90 content::TestBrowserThread ui_thread_; 91 content::TestBrowserThread file_thread_; 92 TestingProfile profile_; 93 94 MenuManager manager_; 95 ExtensionList extensions_; 96 TestExtensionPrefs prefs_; 97 int next_id_; 98 99 private: 100 DISALLOW_COPY_AND_ASSIGN(MenuManagerTest); 101 }; 102 103 // Tests adding, getting, and removing items. 104 TEST_F(MenuManagerTest, AddGetRemoveItems) { 105 Extension* extension = AddExtension("test"); 106 107 // Add a new item, make sure you can get it back. 108 MenuItem* item1 = CreateTestItem(extension); 109 ASSERT_TRUE(item1 != NULL); 110 ASSERT_TRUE(manager_.AddContextItem(extension, item1)); 111 ASSERT_EQ(item1, manager_.GetItemById(item1->id())); 112 const MenuItem::List* items = manager_.MenuItems(item1->extension_id()); 113 ASSERT_EQ(1u, items->size()); 114 ASSERT_EQ(item1, items->at(0)); 115 116 // Add a second item, make sure it comes back too. 117 MenuItem* item2 = CreateTestItemWithID(extension, "id2"); 118 ASSERT_TRUE(manager_.AddContextItem(extension, item2)); 119 ASSERT_EQ(item2, manager_.GetItemById(item2->id())); 120 items = manager_.MenuItems(item2->extension_id()); 121 ASSERT_EQ(2u, items->size()); 122 ASSERT_EQ(item1, items->at(0)); 123 ASSERT_EQ(item2, items->at(1)); 124 125 // Try adding item 3, then removing it. 126 MenuItem* item3 = CreateTestItem(extension); 127 MenuItem::Id id3 = item3->id(); 128 std::string extension_id = item3->extension_id(); 129 ASSERT_TRUE(manager_.AddContextItem(extension, item3)); 130 ASSERT_EQ(item3, manager_.GetItemById(id3)); 131 ASSERT_EQ(3u, manager_.MenuItems(extension_id)->size()); 132 ASSERT_TRUE(manager_.RemoveContextMenuItem(id3)); 133 ASSERT_EQ(NULL, manager_.GetItemById(id3)); 134 ASSERT_EQ(2u, manager_.MenuItems(extension_id)->size()); 135 136 // Make sure removing a non-existent item returns false. 137 MenuItem::Id id(false, extension->id()); 138 id.uid = id3.uid + 50; 139 ASSERT_FALSE(manager_.RemoveContextMenuItem(id)); 140 141 // Make sure adding an item with the same string ID returns false. 142 scoped_ptr<MenuItem> item2too(CreateTestItemWithID(extension, "id2")); 143 ASSERT_FALSE(manager_.AddContextItem(extension, item2too.get())); 144 145 // But the same string ID should not collide with another extension. 146 Extension* extension2 = AddExtension("test2"); 147 MenuItem* item2other = CreateTestItemWithID(extension2, "id2"); 148 ASSERT_TRUE(manager_.AddContextItem(extension2, item2other)); 149 } 150 151 // Test adding/removing child items. 152 TEST_F(MenuManagerTest, ChildFunctions) { 153 Extension* extension1 = AddExtension("1111"); 154 Extension* extension2 = AddExtension("2222"); 155 Extension* extension3 = AddExtension("3333"); 156 157 MenuItem* item1 = CreateTestItem(extension1); 158 MenuItem* item2 = CreateTestItem(extension2); 159 MenuItem* item2_child = CreateTestItemWithID(extension2, "2child"); 160 MenuItem* item2_grandchild = CreateTestItem(extension2); 161 162 // This third item we expect to fail inserting, so we use a scoped_ptr to make 163 // sure it gets deleted. 164 scoped_ptr<MenuItem> item3(CreateTestItem(extension3)); 165 166 // Add in the first two items. 167 ASSERT_TRUE(manager_.AddContextItem(extension1, item1)); 168 ASSERT_TRUE(manager_.AddContextItem(extension2, item2)); 169 170 MenuItem::Id id1 = item1->id(); 171 MenuItem::Id id2 = item2->id(); 172 173 // Try adding item3 as a child of item2 - this should fail because item3 has 174 // a different extension id. 175 ASSERT_FALSE(manager_.AddChildItem(id2, item3.get())); 176 177 // Add item2_child as a child of item2. 178 MenuItem::Id id2_child = item2_child->id(); 179 ASSERT_TRUE(manager_.AddChildItem(id2, item2_child)); 180 ASSERT_EQ(1, item2->child_count()); 181 ASSERT_EQ(0, item1->child_count()); 182 ASSERT_EQ(item2_child, manager_.GetItemById(id2_child)); 183 184 ASSERT_EQ(1u, manager_.MenuItems(item1->extension_id())->size()); 185 ASSERT_EQ(item1, manager_.MenuItems(item1->extension_id())->at(0)); 186 187 // Add item2_grandchild as a child of item2_child, then remove it. 188 MenuItem::Id id2_grandchild = item2_grandchild->id(); 189 ASSERT_TRUE(manager_.AddChildItem(id2_child, item2_grandchild)); 190 ASSERT_EQ(1, item2->child_count()); 191 ASSERT_EQ(1, item2_child->child_count()); 192 ASSERT_TRUE(manager_.RemoveContextMenuItem(id2_grandchild)); 193 194 // We should only get 1 thing back when asking for item2's extension id, since 195 // it has a child item. 196 ASSERT_EQ(1u, manager_.MenuItems(item2->extension_id())->size()); 197 ASSERT_EQ(item2, manager_.MenuItems(item2->extension_id())->at(0)); 198 199 // Remove child2_item. 200 ASSERT_TRUE(manager_.RemoveContextMenuItem(id2_child)); 201 ASSERT_EQ(1u, manager_.MenuItems(item2->extension_id())->size()); 202 ASSERT_EQ(item2, manager_.MenuItems(item2->extension_id())->at(0)); 203 ASSERT_EQ(0, item2->child_count()); 204 } 205 206 TEST_F(MenuManagerTest, PopulateFromValue) { 207 Extension* extension = AddExtension("test"); 208 209 bool incognito = true; 210 int type = MenuItem::CHECKBOX; 211 std::string title("TITLE"); 212 bool checked = true; 213 bool enabled = true; 214 MenuItem::ContextList contexts; 215 contexts.Add(MenuItem::PAGE); 216 contexts.Add(MenuItem::SELECTION); 217 int contexts_value = 0; 218 ASSERT_TRUE(contexts.ToValue()->GetAsInteger(&contexts_value)); 219 220 base::ListValue* document_url_patterns(new base::ListValue()); 221 document_url_patterns->Append( 222 new base::StringValue("http://www.google.com/*")); 223 document_url_patterns->Append( 224 new base::StringValue("http://www.reddit.com/*")); 225 226 base::ListValue* target_url_patterns(new base::ListValue()); 227 target_url_patterns->Append( 228 new base::StringValue("http://www.yahoo.com/*")); 229 target_url_patterns->Append( 230 new base::StringValue("http://www.facebook.com/*")); 231 232 base::DictionaryValue value; 233 value.SetBoolean("incognito", incognito); 234 value.SetString("string_uid", std::string()); 235 value.SetInteger("type", type); 236 value.SetString("title", title); 237 value.SetBoolean("checked", checked); 238 value.SetBoolean("enabled", enabled); 239 value.SetInteger("contexts", contexts_value); 240 value.Set("document_url_patterns", document_url_patterns); 241 value.Set("target_url_patterns", target_url_patterns); 242 243 std::string error; 244 scoped_ptr<MenuItem> item(MenuItem::Populate(extension->id(), value, &error)); 245 ASSERT_TRUE(item.get()); 246 247 EXPECT_EQ(extension->id(), item->extension_id()); 248 EXPECT_EQ(incognito, item->incognito()); 249 EXPECT_EQ(title, item->title()); 250 EXPECT_EQ(checked, item->checked()); 251 EXPECT_EQ(item->checked(), item->checked()); 252 EXPECT_EQ(enabled, item->enabled()); 253 EXPECT_EQ(contexts, item->contexts()); 254 255 URLPatternSet document_url_pattern_set; 256 document_url_pattern_set.Populate(*document_url_patterns, 257 URLPattern::SCHEME_ALL, 258 true, 259 &error); 260 EXPECT_EQ(document_url_pattern_set, item->document_url_patterns()); 261 262 URLPatternSet target_url_pattern_set; 263 target_url_pattern_set.Populate(*target_url_patterns, 264 URLPattern::SCHEME_ALL, 265 true, 266 &error); 267 EXPECT_EQ(target_url_pattern_set, item->target_url_patterns()); 268 } 269 270 // Tests that deleting a parent properly removes descendants. 271 TEST_F(MenuManagerTest, DeleteParent) { 272 Extension* extension = AddExtension("1111"); 273 274 // Set up 5 items to add. 275 MenuItem* item1 = CreateTestItem(extension); 276 MenuItem* item2 = CreateTestItem(extension); 277 MenuItem* item3 = CreateTestItemWithID(extension, "id3"); 278 MenuItem* item4 = CreateTestItemWithID(extension, "id4"); 279 MenuItem* item5 = CreateTestItem(extension); 280 MenuItem* item6 = CreateTestItem(extension); 281 MenuItem::Id item1_id = item1->id(); 282 MenuItem::Id item2_id = item2->id(); 283 MenuItem::Id item3_id = item3->id(); 284 MenuItem::Id item4_id = item4->id(); 285 MenuItem::Id item5_id = item5->id(); 286 MenuItem::Id item6_id = item6->id(); 287 288 // Add the items in the hierarchy 289 // item1 -> item2 -> item3 -> item4 -> item5 -> item6. 290 ASSERT_TRUE(manager_.AddContextItem(extension, item1)); 291 ASSERT_TRUE(manager_.AddChildItem(item1_id, item2)); 292 ASSERT_TRUE(manager_.AddChildItem(item2_id, item3)); 293 ASSERT_TRUE(manager_.AddChildItem(item3_id, item4)); 294 ASSERT_TRUE(manager_.AddChildItem(item4_id, item5)); 295 ASSERT_TRUE(manager_.AddChildItem(item5_id, item6)); 296 ASSERT_EQ(item1, manager_.GetItemById(item1_id)); 297 ASSERT_EQ(item2, manager_.GetItemById(item2_id)); 298 ASSERT_EQ(item3, manager_.GetItemById(item3_id)); 299 ASSERT_EQ(item4, manager_.GetItemById(item4_id)); 300 ASSERT_EQ(item5, manager_.GetItemById(item5_id)); 301 ASSERT_EQ(item6, manager_.GetItemById(item6_id)); 302 ASSERT_EQ(1u, manager_.MenuItems(extension->id())->size()); 303 ASSERT_EQ(6u, manager_.items_by_id_.size()); 304 305 // Remove item6 (a leaf node). 306 ASSERT_TRUE(manager_.RemoveContextMenuItem(item6_id)); 307 ASSERT_EQ(item1, manager_.GetItemById(item1_id)); 308 ASSERT_EQ(item2, manager_.GetItemById(item2_id)); 309 ASSERT_EQ(item3, manager_.GetItemById(item3_id)); 310 ASSERT_EQ(item4, manager_.GetItemById(item4_id)); 311 ASSERT_EQ(item5, manager_.GetItemById(item5_id)); 312 ASSERT_EQ(NULL, manager_.GetItemById(item6_id)); 313 ASSERT_EQ(1u, manager_.MenuItems(extension->id())->size()); 314 ASSERT_EQ(5u, manager_.items_by_id_.size()); 315 316 // Remove item4 and make sure item5 is gone as well. 317 ASSERT_TRUE(manager_.RemoveContextMenuItem(item4_id)); 318 ASSERT_EQ(item1, manager_.GetItemById(item1_id)); 319 ASSERT_EQ(item2, manager_.GetItemById(item2_id)); 320 ASSERT_EQ(item3, manager_.GetItemById(item3_id)); 321 ASSERT_EQ(NULL, manager_.GetItemById(item4_id)); 322 ASSERT_EQ(NULL, manager_.GetItemById(item5_id)); 323 ASSERT_EQ(1u, manager_.MenuItems(extension->id())->size()); 324 ASSERT_EQ(3u, manager_.items_by_id_.size()); 325 326 // Now remove item1 and make sure item2 and item3 are gone as well. 327 ASSERT_TRUE(manager_.RemoveContextMenuItem(item1_id)); 328 ASSERT_EQ(NULL, manager_.MenuItems(extension->id())); 329 ASSERT_EQ(0u, manager_.items_by_id_.size()); 330 ASSERT_EQ(NULL, manager_.GetItemById(item1_id)); 331 ASSERT_EQ(NULL, manager_.GetItemById(item2_id)); 332 ASSERT_EQ(NULL, manager_.GetItemById(item3_id)); 333 } 334 335 // Tests changing parents. 336 TEST_F(MenuManagerTest, ChangeParent) { 337 Extension* extension1 = AddExtension("1111"); 338 339 // First create two items and add them both to the manager. 340 MenuItem* item1 = CreateTestItem(extension1); 341 MenuItem* item2 = CreateTestItem(extension1); 342 343 ASSERT_TRUE(manager_.AddContextItem(extension1, item1)); 344 ASSERT_TRUE(manager_.AddContextItem(extension1, item2)); 345 346 const MenuItem::List* items = manager_.MenuItems(item1->extension_id()); 347 ASSERT_EQ(2u, items->size()); 348 ASSERT_EQ(item1, items->at(0)); 349 ASSERT_EQ(item2, items->at(1)); 350 351 // Now create a third item, initially add it as a child of item1, then move 352 // it to be a child of item2. 353 MenuItem* item3 = CreateTestItem(extension1); 354 355 ASSERT_TRUE(manager_.AddChildItem(item1->id(), item3)); 356 ASSERT_EQ(1, item1->child_count()); 357 ASSERT_EQ(item3, item1->children()[0]); 358 359 ASSERT_TRUE(manager_.ChangeParent(item3->id(), &item2->id())); 360 ASSERT_EQ(0, item1->child_count()); 361 ASSERT_EQ(1, item2->child_count()); 362 ASSERT_EQ(item3, item2->children()[0]); 363 364 // Move item2 to be a child of item1. 365 ASSERT_TRUE(manager_.ChangeParent(item2->id(), &item1->id())); 366 ASSERT_EQ(1, item1->child_count()); 367 ASSERT_EQ(item2, item1->children()[0]); 368 ASSERT_EQ(1, item2->child_count()); 369 ASSERT_EQ(item3, item2->children()[0]); 370 371 // Since item2 was a top-level item but is no longer, we should only have 1 372 // top-level item. 373 items = manager_.MenuItems(item1->extension_id()); 374 ASSERT_EQ(1u, items->size()); 375 ASSERT_EQ(item1, items->at(0)); 376 377 // Move item3 back to being a child of item1, so it's now a sibling of item2. 378 ASSERT_TRUE(manager_.ChangeParent(item3->id(), &item1->id())); 379 ASSERT_EQ(2, item1->child_count()); 380 ASSERT_EQ(item2, item1->children()[0]); 381 ASSERT_EQ(item3, item1->children()[1]); 382 383 // Try switching item3 to be the parent of item1 - this should fail. 384 ASSERT_FALSE(manager_.ChangeParent(item1->id(), &item3->id())); 385 ASSERT_EQ(0, item3->child_count()); 386 ASSERT_EQ(2, item1->child_count()); 387 ASSERT_EQ(item2, item1->children()[0]); 388 ASSERT_EQ(item3, item1->children()[1]); 389 items = manager_.MenuItems(item1->extension_id()); 390 ASSERT_EQ(1u, items->size()); 391 ASSERT_EQ(item1, items->at(0)); 392 393 // Move item2 to be a top-level item. 394 ASSERT_TRUE(manager_.ChangeParent(item2->id(), NULL)); 395 items = manager_.MenuItems(item1->extension_id()); 396 ASSERT_EQ(2u, items->size()); 397 ASSERT_EQ(item1, items->at(0)); 398 ASSERT_EQ(item2, items->at(1)); 399 ASSERT_EQ(1, item1->child_count()); 400 ASSERT_EQ(item3, item1->children()[0]); 401 402 // Make sure you can't move a node to be a child of another extension's item. 403 Extension* extension2 = AddExtension("2222"); 404 MenuItem* item4 = CreateTestItem(extension2); 405 ASSERT_TRUE(manager_.AddContextItem(extension2, item4)); 406 ASSERT_FALSE(manager_.ChangeParent(item4->id(), &item1->id())); 407 ASSERT_FALSE(manager_.ChangeParent(item1->id(), &item4->id())); 408 409 // Make sure you can't make an item be it's own parent. 410 ASSERT_FALSE(manager_.ChangeParent(item1->id(), &item1->id())); 411 } 412 413 // Tests that we properly remove an extension's menu item when that extension is 414 // unloaded. 415 TEST_F(MenuManagerTest, ExtensionUnloadRemovesMenuItems) { 416 content::NotificationService* notifier = 417 content::NotificationService::current(); 418 ASSERT_TRUE(notifier != NULL); 419 420 // Create a test extension. 421 Extension* extension1 = AddExtension("1111"); 422 423 // Create an MenuItem and put it into the manager. 424 MenuItem* item1 = CreateTestItem(extension1); 425 MenuItem::Id id1 = item1->id(); 426 ASSERT_EQ(extension1->id(), item1->extension_id()); 427 ASSERT_TRUE(manager_.AddContextItem(extension1, item1)); 428 ASSERT_EQ(1u, manager_.MenuItems(extension1->id())->size()); 429 430 // Create a menu item with a different extension id and add it to the manager. 431 Extension* extension2 = AddExtension("2222"); 432 MenuItem* item2 = CreateTestItem(extension2); 433 ASSERT_NE(item1->extension_id(), item2->extension_id()); 434 ASSERT_TRUE(manager_.AddContextItem(extension2, item2)); 435 436 // Notify that the extension was unloaded, and make sure the right item is 437 // gone. 438 UnloadedExtensionInfo details( 439 extension1, UnloadedExtensionInfo::REASON_DISABLE); 440 notifier->Notify(chrome::NOTIFICATION_EXTENSION_UNLOADED, 441 content::Source<Profile>(&profile_), 442 content::Details<UnloadedExtensionInfo>( 443 &details)); 444 ASSERT_EQ(NULL, manager_.MenuItems(extension1->id())); 445 ASSERT_EQ(1u, manager_.MenuItems(extension2->id())->size()); 446 ASSERT_TRUE(manager_.GetItemById(id1) == NULL); 447 ASSERT_TRUE(manager_.GetItemById(item2->id()) != NULL); 448 } 449 450 // A mock message service for tests of MenuManager::ExecuteCommand. 451 class MockEventRouter : public EventRouter { 452 public: 453 explicit MockEventRouter(Profile* profile) : EventRouter(profile, NULL) {} 454 455 MOCK_METHOD6(DispatchEventToExtensionMock, 456 void(const std::string& extension_id, 457 const std::string& event_name, 458 base::ListValue* event_args, 459 content::BrowserContext* source_context, 460 const GURL& event_url, 461 EventRouter::UserGestureState state)); 462 463 virtual void DispatchEventToExtension(const std::string& extension_id, 464 scoped_ptr<Event> event) { 465 DispatchEventToExtensionMock(extension_id, 466 event->event_name, 467 event->event_args.release(), 468 event->restrict_to_browser_context, 469 event->event_url, 470 event->user_gesture); 471 } 472 473 private: 474 DISALLOW_COPY_AND_ASSIGN(MockEventRouter); 475 }; 476 477 // A mock ExtensionSystem to serve our MockEventRouter. 478 class MockExtensionSystem : public TestExtensionSystem { 479 public: 480 explicit MockExtensionSystem(Profile* profile) 481 : TestExtensionSystem(profile) {} 482 483 virtual EventRouter* event_router() OVERRIDE { 484 if (!mock_event_router_) 485 mock_event_router_.reset(new MockEventRouter(profile_)); 486 return mock_event_router_.get(); 487 } 488 489 private: 490 scoped_ptr<MockEventRouter> mock_event_router_; 491 492 DISALLOW_COPY_AND_ASSIGN(MockExtensionSystem); 493 }; 494 495 BrowserContextKeyedService* BuildMockExtensionSystem( 496 content::BrowserContext* profile) { 497 return new MockExtensionSystem(static_cast<Profile*>(profile)); 498 } 499 500 // Tests the RemoveAll functionality. 501 TEST_F(MenuManagerTest, RemoveAll) { 502 // Try removing all items for an extension id that doesn't have any items. 503 manager_.RemoveAllContextItems("CCCC"); 504 505 // Add 2 top-level and one child item for extension 1. 506 Extension* extension1 = AddExtension("1111"); 507 MenuItem* item1 = CreateTestItem(extension1); 508 MenuItem* item2 = CreateTestItem(extension1); 509 MenuItem* item3 = CreateTestItem(extension1); 510 ASSERT_TRUE(manager_.AddContextItem(extension1, item1)); 511 ASSERT_TRUE(manager_.AddContextItem(extension1, item2)); 512 ASSERT_TRUE(manager_.AddChildItem(item1->id(), item3)); 513 514 // Add one top-level item for extension 2. 515 Extension* extension2 = AddExtension("2222"); 516 MenuItem* item4 = CreateTestItem(extension2); 517 ASSERT_TRUE(manager_.AddContextItem(extension2, item4)); 518 519 EXPECT_EQ(2u, manager_.MenuItems(extension1->id())->size()); 520 EXPECT_EQ(1u, manager_.MenuItems(extension2->id())->size()); 521 522 // Remove extension2's item. 523 manager_.RemoveAllContextItems(extension2->id()); 524 EXPECT_EQ(2u, manager_.MenuItems(extension1->id())->size()); 525 EXPECT_EQ(NULL, manager_.MenuItems(extension2->id())); 526 527 // Remove extension1's items. 528 manager_.RemoveAllContextItems(extension1->id()); 529 EXPECT_EQ(NULL, manager_.MenuItems(extension1->id())); 530 } 531 532 // Tests that removing all items one-by-one doesn't leave an entry around. 533 TEST_F(MenuManagerTest, RemoveOneByOne) { 534 // Add 2 test items. 535 Extension* extension1 = AddExtension("1111"); 536 MenuItem* item1 = CreateTestItem(extension1); 537 MenuItem* item2 = CreateTestItem(extension1); 538 MenuItem* item3 = CreateTestItemWithID(extension1, "id3"); 539 ASSERT_TRUE(manager_.AddContextItem(extension1, item1)); 540 ASSERT_TRUE(manager_.AddContextItem(extension1, item2)); 541 ASSERT_TRUE(manager_.AddContextItem(extension1, item3)); 542 543 ASSERT_FALSE(manager_.context_items_.empty()); 544 545 manager_.RemoveContextMenuItem(item3->id()); 546 manager_.RemoveContextMenuItem(item1->id()); 547 manager_.RemoveContextMenuItem(item2->id()); 548 549 ASSERT_TRUE(manager_.context_items_.empty()); 550 } 551 552 TEST_F(MenuManagerTest, ExecuteCommand) { 553 TestingProfile profile; 554 555 MockExtensionSystem* mock_extension_system = 556 static_cast<MockExtensionSystem*>(ExtensionSystemFactory::GetInstance()-> 557 SetTestingFactoryAndUse(&profile, &BuildMockExtensionSystem)); 558 MockEventRouter* mock_event_router = 559 static_cast<MockEventRouter*>(mock_extension_system->event_router()); 560 561 content::ContextMenuParams params; 562 params.media_type = blink::WebContextMenuData::MediaTypeImage; 563 params.src_url = GURL("http://foo.bar/image.png"); 564 params.page_url = GURL("http://foo.bar"); 565 params.selection_text = ASCIIToUTF16("Hello World"); 566 params.is_editable = false; 567 568 Extension* extension = AddExtension("test"); 569 MenuItem* parent = CreateTestItem(extension); 570 MenuItem* item = CreateTestItem(extension); 571 MenuItem::Id parent_id = parent->id(); 572 MenuItem::Id id = item->id(); 573 ASSERT_TRUE(manager_.AddContextItem(extension, parent)); 574 ASSERT_TRUE(manager_.AddChildItem(parent->id(), item)); 575 576 // Use the magic of googlemock to save a parameter to our mock's 577 // DispatchEventToExtension method into event_args. 578 base::ListValue* list = NULL; 579 { 580 InSequence s; 581 EXPECT_CALL(*mock_event_router, 582 DispatchEventToExtensionMock( 583 item->extension_id(), 584 extensions::event_names::kOnContextMenus, 585 _, 586 &profile, 587 GURL(), 588 EventRouter::USER_GESTURE_ENABLED)) 589 .Times(1) 590 .WillOnce(SaveArg<2>(&list)); 591 EXPECT_CALL(*mock_event_router, 592 DispatchEventToExtensionMock( 593 item->extension_id(), 594 context_menus::OnClicked::kEventName, 595 _, 596 &profile, 597 GURL(), 598 EventRouter::USER_GESTURE_ENABLED)) 599 .Times(1) 600 .WillOnce(DeleteArg<2>()); 601 } 602 manager_.ExecuteCommand(&profile, NULL /* web_contents */, params, id); 603 604 ASSERT_EQ(2u, list->GetSize()); 605 606 DictionaryValue* info; 607 ASSERT_TRUE(list->GetDictionary(0, &info)); 608 609 int tmp_id = 0; 610 ASSERT_TRUE(info->GetInteger("menuItemId", &tmp_id)); 611 ASSERT_EQ(id.uid, tmp_id); 612 ASSERT_TRUE(info->GetInteger("parentMenuItemId", &tmp_id)); 613 ASSERT_EQ(parent_id.uid, tmp_id); 614 615 std::string tmp; 616 ASSERT_TRUE(info->GetString("mediaType", &tmp)); 617 ASSERT_EQ("image", tmp); 618 ASSERT_TRUE(info->GetString("srcUrl", &tmp)); 619 ASSERT_EQ(params.src_url.spec(), tmp); 620 ASSERT_TRUE(info->GetString("pageUrl", &tmp)); 621 ASSERT_EQ(params.page_url.spec(), tmp); 622 623 base::string16 tmp16; 624 ASSERT_TRUE(info->GetString("selectionText", &tmp16)); 625 ASSERT_EQ(params.selection_text, tmp16); 626 627 bool bool_tmp = true; 628 ASSERT_TRUE(info->GetBoolean("editable", &bool_tmp)); 629 ASSERT_EQ(params.is_editable, bool_tmp); 630 631 delete list; 632 } 633 634 // Test that there is always only one radio item selected. 635 TEST_F(MenuManagerTest, SanitizeRadioButtons) { 636 Extension* extension = AddExtension("test"); 637 638 // A single unchecked item should get checked 639 MenuItem* item1 = CreateTestItem(extension); 640 641 item1->set_type(MenuItem::RADIO); 642 item1->SetChecked(false); 643 ASSERT_FALSE(item1->checked()); 644 manager_.AddContextItem(extension, item1); 645 ASSERT_TRUE(item1->checked()); 646 647 // In a run of two unchecked items, the first should get selected. 648 item1->SetChecked(false); 649 MenuItem* item2 = CreateTestItem(extension); 650 item2->set_type(MenuItem::RADIO); 651 item2->SetChecked(false); 652 ASSERT_FALSE(item1->checked()); 653 ASSERT_FALSE(item2->checked()); 654 manager_.AddContextItem(extension, item2); 655 ASSERT_TRUE(item1->checked()); 656 ASSERT_FALSE(item2->checked()); 657 658 // If multiple items are checked, only the last item should get checked. 659 item1->SetChecked(true); 660 item2->SetChecked(true); 661 ASSERT_TRUE(item1->checked()); 662 ASSERT_TRUE(item2->checked()); 663 manager_.ItemUpdated(item1->id()); 664 ASSERT_FALSE(item1->checked()); 665 ASSERT_TRUE(item2->checked()); 666 667 // If the checked item is removed, the new first item should get checked. 668 item1->SetChecked(false); 669 item2->SetChecked(true); 670 ASSERT_FALSE(item1->checked()); 671 ASSERT_TRUE(item2->checked()); 672 manager_.RemoveContextMenuItem(item2->id()); 673 item2 = NULL; 674 ASSERT_TRUE(item1->checked()); 675 676 // If a checked item is added to a run that already has a checked item, 677 // then the new item should get checked. 678 item1->SetChecked(true); 679 MenuItem* new_item = CreateTestItem(extension); 680 new_item->set_type(MenuItem::RADIO); 681 new_item->SetChecked(true); 682 ASSERT_TRUE(item1->checked()); 683 ASSERT_TRUE(new_item->checked()); 684 manager_.AddContextItem(extension, new_item); 685 ASSERT_FALSE(item1->checked()); 686 ASSERT_TRUE(new_item->checked()); 687 // Make sure that children are checked as well. 688 MenuItem* parent = CreateTestItem(extension); 689 manager_.AddContextItem(extension, parent); 690 MenuItem* child1 = CreateTestItem(extension); 691 child1->set_type(MenuItem::RADIO); 692 child1->SetChecked(false); 693 MenuItem* child2 = CreateTestItem(extension); 694 child2->set_type(MenuItem::RADIO); 695 child2->SetChecked(true); 696 ASSERT_FALSE(child1->checked()); 697 ASSERT_TRUE(child2->checked()); 698 699 manager_.AddChildItem(parent->id(), child1); 700 ASSERT_TRUE(child1->checked()); 701 702 manager_.AddChildItem(parent->id(), child2); 703 ASSERT_FALSE(child1->checked()); 704 ASSERT_TRUE(child2->checked()); 705 706 // Removing the checked item from the children should cause the 707 // remaining child to be checked. 708 manager_.RemoveContextMenuItem(child2->id()); 709 child2 = NULL; 710 ASSERT_TRUE(child1->checked()); 711 712 // This should NOT cause |new_item| to be deseleted because 713 // |parent| will be seperating the two runs of radio items. 714 manager_.ChangeParent(child1->id(), NULL); 715 ASSERT_TRUE(new_item->checked()); 716 ASSERT_TRUE(child1->checked()); 717 718 // Removing |parent| should cause only |child1| to be selected. 719 manager_.RemoveContextMenuItem(parent->id()); 720 parent = NULL; 721 ASSERT_FALSE(new_item->checked()); 722 ASSERT_TRUE(child1->checked()); 723 } 724 725 // Tests the RemoveAllIncognitoContextItems functionality. 726 TEST_F(MenuManagerTest, RemoveAllIncognito) { 727 Extension* extension1 = AddExtension("1111"); 728 // Add 2 top-level and one child item for extension 1 729 // with incognito 'true'. 730 MenuItem* item1 = CreateTestItem(extension1, true); 731 MenuItem* item2 = CreateTestItem(extension1, true); 732 MenuItem* item3 = CreateTestItem(extension1, true); 733 ASSERT_TRUE(manager_.AddContextItem(extension1, item1)); 734 ASSERT_TRUE(manager_.AddContextItem(extension1, item2)); 735 ASSERT_TRUE(manager_.AddChildItem(item1->id(), item3)); 736 737 // Add 2 top-level and one child item for extension 1 738 // with incognito 'false'. 739 MenuItem* item4 = CreateTestItem(extension1); 740 MenuItem* item5 = CreateTestItem(extension1); 741 MenuItem* item6 = CreateTestItem(extension1); 742 ASSERT_TRUE(manager_.AddContextItem(extension1, item4)); 743 ASSERT_TRUE(manager_.AddContextItem(extension1, item5)); 744 ASSERT_TRUE(manager_.AddChildItem(item4->id(), item6)); 745 746 // Add one top-level item for extension 2. 747 Extension* extension2 = AddExtension("2222"); 748 MenuItem* item7 = CreateTestItem(extension2); 749 ASSERT_TRUE(manager_.AddContextItem(extension2, item7)); 750 751 EXPECT_EQ(4u, manager_.MenuItems(extension1->id())->size()); 752 EXPECT_EQ(1u, manager_.MenuItems(extension2->id())->size()); 753 754 // Remove all context menu items with incognito true. 755 manager_.RemoveAllIncognitoContextItems(); 756 EXPECT_EQ(2u, manager_.MenuItems(extension1->id())->size()); 757 EXPECT_EQ(1u, manager_.MenuItems(extension2->id())->size()); 758 } 759 760 } // namespace extensions 761