extension_context_menu_browsertest.cc revision 3345a6884c488ff3a535c2c9acdd33d74b37e311
1// Copyright (c) 2010 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 "app/menus/menu_model.h" 6#include "chrome/app/chrome_dll_resource.h" 7#include "chrome/browser/browser.h" 8#include "chrome/browser/extensions/extension_browsertest.h" 9#include "chrome/browser/extensions/extension_test_message_listener.h" 10#include "chrome/browser/extensions/extensions_service.h" 11#include "chrome/browser/profile.h" 12#include "chrome/browser/tab_contents/render_view_context_menu.h" 13#include "chrome/browser/tab_contents/tab_contents.h" 14#include "chrome/common/chrome_switches.h" 15#include "chrome/test/ui_test_utils.h" 16#include "net/base/mock_host_resolver.h" 17#include "third_party/WebKit/WebKit/chromium/public/WebContextMenuData.h" 18#include "webkit/glue/context_menu.h" 19 20using menus::MenuModel; 21using WebKit::WebContextMenuData; 22 23// This test class helps us sidestep platform-specific issues with popping up a 24// real context menu, while still running through the actual code in 25// RenderViewContextMenu where extension items get added and executed. 26class TestRenderViewContextMenu : public RenderViewContextMenu { 27 public: 28 TestRenderViewContextMenu(TabContents* tab_contents, 29 const ContextMenuParams& params) 30 : RenderViewContextMenu(tab_contents, params) {} 31 32 virtual ~TestRenderViewContextMenu() {} 33 34 bool HasExtensionItemWithLabel(const std::string& label) { 35 string16 label16 = UTF8ToUTF16(label); 36 std::map<int, ExtensionMenuItem::Id>::iterator i; 37 for (i = extension_item_map_.begin(); i != extension_item_map_.end(); ++i) { 38 const ExtensionMenuItem::Id& id = i->second; 39 string16 tmp_label; 40 EXPECT_TRUE(GetItemLabel(id, &tmp_label)); 41 if (tmp_label == label16) 42 return true; 43 } 44 return false; 45 } 46 47 // Looks in the menu for an extension item with |id|, and if it is found and 48 // has a label, that is put in |result| and we return true. Otherwise returns 49 // false. 50 bool GetItemLabel(const ExtensionMenuItem::Id& id, string16* result) { 51 int command_id = 0; 52 if (!FindCommandId(id, &command_id)) 53 return false; 54 55 MenuModel* model = NULL; 56 int index = -1; 57 if (!GetMenuModelAndItemIndex(command_id, &model, &index)) { 58 return false; 59 } 60 *result = model->GetLabelAt(index); 61 return true; 62 } 63 64 // Searches for an menu item with |command_id|. If it's found, the return 65 // value is true and the model and index where it appears in that model are 66 // returned in |found_model| and |found_index|. Otherwise returns false. 67 bool GetMenuModelAndItemIndex(int command_id, 68 MenuModel** found_model, 69 int *found_index) { 70 std::vector<MenuModel*> models_to_search; 71 models_to_search.push_back(&menu_model_); 72 73 while (!models_to_search.empty()) { 74 MenuModel* model = models_to_search.back(); 75 models_to_search.pop_back(); 76 for (int i = 0; i < model->GetItemCount(); i++) { 77 if (model->GetCommandIdAt(i) == command_id) { 78 *found_model = model; 79 *found_index = i; 80 return true; 81 } else if (model->GetTypeAt(i) == MenuModel::TYPE_SUBMENU) { 82 models_to_search.push_back(model->GetSubmenuModelAt(i)); 83 } 84 } 85 } 86 87 return false; 88 } 89 90 protected: 91 // These two functions implement pure virtual methods of 92 // RenderViewContextMenu. 93 virtual bool GetAcceleratorForCommandId(int command_id, 94 menus::Accelerator* accelerator) { 95 // None of our commands have accelerators, so always return false. 96 return false; 97 } 98 virtual void PlatformInit() {} 99 100 101 // Given an extension menu item id, tries to find the corresponding command id 102 // in the menu. 103 bool FindCommandId(const ExtensionMenuItem::Id& id, int* command_id) { 104 std::map<int, ExtensionMenuItem::Id>::const_iterator i; 105 for (i = extension_item_map_.begin(); i != extension_item_map_.end(); ++i) { 106 if (i->second == id) { 107 *command_id = i->first; 108 return true; 109 } 110 } 111 return false; 112 } 113}; 114 115class ExtensionContextMenuBrowserTest : public ExtensionBrowserTest { 116 public: 117 // Helper to load an extension from context_menus/|subdirectory| in the 118 // extensions test data dir. 119 bool LoadContextMenuExtension(std::string subdirectory) { 120 FilePath extension_dir = 121 test_data_dir_.AppendASCII("context_menus").AppendASCII(subdirectory); 122 return LoadExtension(extension_dir); 123 } 124 125 TestRenderViewContextMenu* CreateMenu(const GURL& page_url, 126 const GURL& link_url) { 127 TabContents* tab_contents = browser()->GetSelectedTabContents(); 128 WebContextMenuData data; 129 ContextMenuParams params(data); 130 params.page_url = page_url; 131 params.link_url = link_url; 132 TestRenderViewContextMenu* menu = 133 new TestRenderViewContextMenu(tab_contents, params); 134 menu->Init(); 135 return menu; 136 } 137 138 // Shortcut to return the current ExtensionMenuManager. 139 ExtensionMenuManager* menu_manager() { 140 return browser()->profile()->GetExtensionsService()->menu_manager(); 141 } 142 143 // Returns a pointer to the currently loaded extension with |name|, or null 144 // if not found. 145 Extension* GetExtensionNamed(std::string name) { 146 const ExtensionList* extensions = 147 browser()->profile()->GetExtensionsService()->extensions(); 148 ExtensionList::const_iterator i; 149 for (i = extensions->begin(); i != extensions->end(); ++i) { 150 if ((*i)->name() == name) { 151 return *i; 152 } 153 } 154 return NULL; 155 } 156 157 // This gets all the items that any extension has registered for possible 158 // inclusion in context menus. 159 ExtensionMenuItem::List GetItems() { 160 ExtensionMenuItem::List result; 161 std::set<std::string> extension_ids = menu_manager()->ExtensionIds(); 162 std::set<std::string>::iterator i; 163 for (i = extension_ids.begin(); i != extension_ids.end(); ++i) { 164 const ExtensionMenuItem::List* list = menu_manager()->MenuItems(*i); 165 result.insert(result.end(), list->begin(), list->end()); 166 } 167 return result; 168 } 169 170 // This creates a test menu for a page with |page_url| and |link_url|, looks 171 // for an extension item with the given |label|, and returns true if the item 172 // was found. 173 bool MenuHasItemWithLabel(const GURL& page_url, 174 const GURL& link_url, 175 const std::string& label) { 176 scoped_ptr<TestRenderViewContextMenu> menu(CreateMenu(page_url, link_url)); 177 return menu->HasExtensionItemWithLabel(label); 178 } 179}; 180 181// Tests adding a simple context menu item. 182IN_PROC_BROWSER_TEST_F(ExtensionContextMenuBrowserTest, Simple) { 183 ExtensionTestMessageListener listener1("created item"); 184 ExtensionTestMessageListener listener2("onclick fired"); 185 ASSERT_TRUE(LoadContextMenuExtension("simple")); 186 187 // Wait for the extension to tell us it's created an item. 188 ASSERT_TRUE(listener1.WaitUntilSatisfied()); 189 190 GURL page_url("http://www.google.com"); 191 192 // Create and build our test context menu. 193 scoped_ptr<TestRenderViewContextMenu> menu(CreateMenu(page_url, GURL())); 194 195 // Look for the extension item in the menu, and execute it. 196 int command_id = IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST; 197 ASSERT_TRUE(menu->IsCommandIdEnabled(command_id)); 198 menu->ExecuteCommand(command_id); 199 200 // Wait for the extension's script to tell us its onclick fired. 201 ASSERT_TRUE(listener2.WaitUntilSatisfied()); 202} 203 204// Tests that setting "documentUrlPatterns" for an item properly restricts 205// those items to matching pages. 206IN_PROC_BROWSER_TEST_F(ExtensionContextMenuBrowserTest, Patterns) { 207 ExtensionTestMessageListener listener("created items"); 208 209 ASSERT_TRUE(LoadContextMenuExtension("patterns")); 210 211 // Wait for the js test code to create its two items with patterns. 212 ASSERT_TRUE(listener.WaitUntilSatisfied()); 213 214 // Check that a document url that should match the items' patterns appears. 215 GURL google_url("http://www.google.com"); 216 ASSERT_TRUE(MenuHasItemWithLabel(google_url, 217 GURL(), 218 std::string("test_item1"))); 219 ASSERT_TRUE(MenuHasItemWithLabel(google_url, 220 GURL(), 221 std::string("test_item2"))); 222 223 // Now check with a non-matching url. 224 GURL test_url("http://www.test.com"); 225 ASSERT_FALSE(MenuHasItemWithLabel(test_url, 226 GURL(), 227 std::string("test_item1"))); 228 ASSERT_FALSE(MenuHasItemWithLabel(test_url, 229 GURL(), 230 std::string("test_item2"))); 231} 232 233// Tests registering an item with a very long title that should get truncated in 234// the actual menu displayed. 235IN_PROC_BROWSER_TEST_F(ExtensionContextMenuBrowserTest, LongTitle) { 236 ExtensionTestMessageListener listener("created"); 237 238 // Load the extension and wait until it's created a menu item. 239 ASSERT_TRUE(LoadContextMenuExtension("long_title")); 240 ASSERT_TRUE(listener.WaitUntilSatisfied()); 241 242 // Make sure we have an item registered with a long title. 243 size_t limit = RenderViewContextMenu::kMaxExtensionItemTitleLength; 244 ExtensionMenuItem::List items = GetItems(); 245 ASSERT_EQ(1u, items.size()); 246 ExtensionMenuItem* item = items.at(0); 247 ASSERT_GT(item->title().size(), limit); 248 249 // Create a context menu, then find the item's label. It should be properly 250 // truncated. 251 GURL url("http://foo.com/"); 252 scoped_ptr<TestRenderViewContextMenu> menu(CreateMenu(url, GURL())); 253 254 string16 label; 255 ASSERT_TRUE(menu->GetItemLabel(item->id(), &label)); 256 ASSERT_TRUE(label.size() <= limit); 257} 258 259// Checks that in |menu|, the item at |index| has type |expected_type| and a 260// label of |expected_label|. 261static void ExpectLabelAndType(const char* expected_label, 262 MenuModel::ItemType expected_type, 263 const MenuModel& menu, 264 int index) { 265 EXPECT_EQ(expected_type, menu.GetTypeAt(index)); 266 EXPECT_EQ(UTF8ToUTF16(expected_label), menu.GetLabelAt(index)); 267} 268 269// In the separators test we build a submenu with items and separators in two 270// different ways - this is used to verify the results in both cases. 271static void VerifyMenuForSeparatorsTest(const MenuModel& menu) { 272 // We expect to see the following items in the menu: 273 // radio1 274 // radio2 275 // --separator-- (automatically added) 276 // normal1 277 // --separator-- 278 // normal2 279 // --separator-- 280 // radio3 281 // radio4 282 // --separator-- 283 // normal3 284 285 int index = 0; 286 ASSERT_EQ(11, menu.GetItemCount()); 287 ExpectLabelAndType("radio1", MenuModel::TYPE_RADIO, menu, index++); 288 ExpectLabelAndType("radio2", MenuModel::TYPE_RADIO, menu, index++); 289 EXPECT_EQ(MenuModel::TYPE_SEPARATOR, menu.GetTypeAt(index++)); 290 ExpectLabelAndType("normal1", MenuModel::TYPE_COMMAND, menu, index++); 291 EXPECT_EQ(MenuModel::TYPE_SEPARATOR, menu.GetTypeAt(index++)); 292 ExpectLabelAndType("normal2", MenuModel::TYPE_COMMAND, menu, index++); 293 EXPECT_EQ(MenuModel::TYPE_SEPARATOR, menu.GetTypeAt(index++)); 294 ExpectLabelAndType("radio3", MenuModel::TYPE_RADIO, menu, index++); 295 ExpectLabelAndType("radio4", MenuModel::TYPE_RADIO, menu, index++); 296 EXPECT_EQ(MenuModel::TYPE_SEPARATOR, menu.GetTypeAt(index++)); 297 ExpectLabelAndType("normal3", MenuModel::TYPE_COMMAND, menu, index++); 298} 299 300// Tests a number of cases for auto-generated and explicitly added separators. 301IN_PROC_BROWSER_TEST_F(ExtensionContextMenuBrowserTest, Separators) { 302 // Load the extension. 303 ASSERT_TRUE(LoadContextMenuExtension("separators")); 304 Extension* extension = GetExtensionNamed("Separators Test"); 305 ASSERT_TRUE(extension != NULL); 306 307 // Navigate to test1.html inside the extension, which should create a bunch 308 // of items at the top-level (but they'll get pushed into an auto-generated 309 // parent). 310 ExtensionTestMessageListener listener1("test1 create finished"); 311 ui_test_utils::NavigateToURL(browser(), 312 GURL(extension->GetResourceURL("test1.html"))); 313 listener1.WaitUntilSatisfied(); 314 315 GURL url("http://www.google.com/"); 316 scoped_ptr<TestRenderViewContextMenu> menu(CreateMenu(url, GURL())); 317 318 // The top-level item should be an "automagic parent" with the extension's 319 // name. 320 MenuModel* model = NULL; 321 int index = 0; 322 string16 label; 323 ASSERT_TRUE(menu->GetMenuModelAndItemIndex( 324 IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST, &model, &index)); 325 EXPECT_EQ(UTF8ToUTF16(extension->name()), model->GetLabelAt(index)); 326 ASSERT_EQ(MenuModel::TYPE_SUBMENU, model->GetTypeAt(index)); 327 328 // Get the submenu and verify the items there. 329 MenuModel* submenu = model->GetSubmenuModelAt(index); 330 ASSERT_TRUE(submenu != NULL); 331 VerifyMenuForSeparatorsTest(*submenu); 332 333 // Now run our second test - navigate to test2.html which creates an explicit 334 // parent node and populates that with the same items as in test1. 335 ExtensionTestMessageListener listener2("test2 create finished"); 336 ui_test_utils::NavigateToURL(browser(), 337 GURL(extension->GetResourceURL("test2.html"))); 338 listener2.WaitUntilSatisfied(); 339 menu.reset(CreateMenu(url, GURL())); 340 ASSERT_TRUE(menu->GetMenuModelAndItemIndex( 341 IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST, &model, &index)); 342 EXPECT_EQ(UTF8ToUTF16("parent"), model->GetLabelAt(index)); 343 submenu = model->GetSubmenuModelAt(index); 344 ASSERT_TRUE(submenu != NULL); 345 VerifyMenuForSeparatorsTest(*submenu); 346} 347 348// Tests that targetUrlPattern keeps items from appearing when there is no 349// target url. 350IN_PROC_BROWSER_TEST_F(ExtensionContextMenuBrowserTest, TargetURLs) { 351 ExtensionTestMessageListener listener("created items"); 352 ASSERT_TRUE(LoadContextMenuExtension("target_urls")); 353 ASSERT_TRUE(listener.WaitUntilSatisfied()); 354 355 GURL google_url("http://www.google.com"); 356 GURL non_google_url("http://www.foo.com"); 357 358 // No target url - the item should not appear. 359 ASSERT_FALSE(MenuHasItemWithLabel(google_url, GURL(), std::string("item1"))); 360 361 // A matching target url - the item should appear. 362 ASSERT_TRUE(MenuHasItemWithLabel(google_url, 363 google_url, 364 std::string("item1"))); 365 366 // A non-matching target url - the item should not appear. 367 ASSERT_FALSE(MenuHasItemWithLabel(google_url, 368 non_google_url, 369 std::string("item1"))); 370} 371