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/browser/extensions/extension_context_menu_model.h" 6 7#include "chrome/browser/extensions/api/extension_action/extension_action_api.h" 8#include "chrome/browser/extensions/extension_service.h" 9#include "chrome/browser/extensions/extension_service_test_base.h" 10#include "chrome/browser/extensions/menu_manager.h" 11#include "chrome/browser/extensions/menu_manager_factory.h" 12#include "chrome/browser/ui/browser.h" 13#include "chrome/browser/ui/host_desktop.h" 14#include "chrome/common/extensions/api/context_menus.h" 15#include "chrome/grit/generated_resources.h" 16#include "chrome/test/base/test_browser_window.h" 17#include "chrome/test/base/testing_profile.h" 18#include "components/crx_file/id_util.h" 19#include "extensions/browser/extension_prefs.h" 20#include "extensions/browser/extension_system.h" 21#include "extensions/browser/test_management_policy.h" 22#include "extensions/common/extension_builder.h" 23#include "extensions/common/feature_switch.h" 24#include "extensions/common/manifest_constants.h" 25#include "extensions/common/value_builder.h" 26#include "testing/gtest/include/gtest/gtest.h" 27#include "ui/base/l10n/l10n_util.h" 28 29namespace extensions { 30 31namespace { 32 33// Build an extension to pass to the menu constructor, with the an action 34// specified by |action_key|. 35scoped_refptr<const Extension> BuildExtension(const std::string& name, 36 const char* action_key) { 37 return ExtensionBuilder() 38 .SetManifest(DictionaryBuilder() 39 .Set("name", name) 40 .Set("version", "1") 41 .Set("manifest_version", 2) 42 .Set(action_key, DictionaryBuilder().Pass())) 43 .SetID(crx_file::id_util::GenerateId(name)) 44 .Build(); 45} 46 47// Create a Browser for the ExtensionContextMenuModel to use. 48scoped_ptr<Browser> CreateBrowser(Profile* profile) { 49 Browser::CreateParams params(profile, chrome::GetActiveDesktop()); 50 TestBrowserWindow test_window; 51 params.window = &test_window; 52 return scoped_ptr<Browser>(new Browser(params)); 53} 54 55// Returns the index of the given |command_id| in the given |menu|, or -1 if it 56// is not found. 57int GetCommandIndex(const scoped_refptr<ExtensionContextMenuModel> menu, 58 int command_id) { 59 int item_count = menu->GetItemCount(); 60 for (int i = 0; i < item_count; ++i) { 61 if (menu->GetCommandIdAt(i) == command_id) 62 return i; 63 } 64 return -1; 65} 66 67} // namespace 68 69class ExtensionContextMenuModelTest : public ExtensionServiceTestBase { 70 public: 71 ExtensionContextMenuModelTest(); 72 73 // Creates an extension menu item for |extension| with the given |context| 74 // and adds it to |manager|. Refreshes |model| to show new item. 75 void AddContextItemAndRefreshModel(MenuManager* manager, 76 const Extension* extension, 77 MenuItem::Context context, 78 ExtensionContextMenuModel* model); 79 80 // Reinitializes the given |model|. 81 void RefreshMenu(ExtensionContextMenuModel* model); 82 83 // Returns the number of extension menu items that show up in |model|. 84 int CountExtensionItems(ExtensionContextMenuModel* model); 85 86 private: 87 int cur_id_; 88}; 89 90ExtensionContextMenuModelTest::ExtensionContextMenuModelTest() : cur_id_(0) { 91} 92 93 94void ExtensionContextMenuModelTest::AddContextItemAndRefreshModel( 95 MenuManager* manager, 96 const Extension* extension, 97 MenuItem::Context context, 98 ExtensionContextMenuModel* model) { 99 MenuItem::Type type = MenuItem::NORMAL; 100 MenuItem::ContextList contexts(context); 101 const MenuItem::ExtensionKey key(extension->id()); 102 MenuItem::Id id(false, key); 103 id.uid = ++cur_id_; 104 manager->AddContextItem(extension, 105 new MenuItem(id, 106 "test", 107 false, // checked 108 true, // enabled 109 type, 110 contexts)); 111 RefreshMenu(model); 112} 113 114void ExtensionContextMenuModelTest::RefreshMenu( 115 ExtensionContextMenuModel* model) { 116 model->InitMenu(model->GetExtension()); 117} 118 119int ExtensionContextMenuModelTest::CountExtensionItems( 120 ExtensionContextMenuModel* model) { 121 return model->extension_items_count_; 122} 123 124// Tests that applicable menu items are disabled when a ManagementPolicy 125// prohibits them. 126TEST_F(ExtensionContextMenuModelTest, PolicyDisablesItems) { 127 InitializeEmptyExtensionService(); 128 scoped_refptr<const Extension> extension = 129 BuildExtension("extension", manifest_keys::kPageAction); 130 ASSERT_TRUE(extension.get()); 131 service()->AddExtension(extension.get()); 132 133 scoped_ptr<Browser> browser = CreateBrowser(profile()); 134 135 scoped_refptr<ExtensionContextMenuModel> menu( 136 new ExtensionContextMenuModel(extension.get(), browser.get())); 137 138 ExtensionSystem* system = ExtensionSystem::Get(profile()); 139 system->management_policy()->UnregisterAllProviders(); 140 141 // Actions should be enabled. 142 ASSERT_TRUE(menu->IsCommandIdEnabled(ExtensionContextMenuModel::UNINSTALL)); 143 144 TestManagementPolicyProvider policy_provider( 145 TestManagementPolicyProvider::PROHIBIT_MODIFY_STATUS); 146 system->management_policy()->RegisterProvider(&policy_provider); 147 148 // Now the actions are disabled. 149 ASSERT_FALSE(menu->IsCommandIdEnabled(ExtensionContextMenuModel::UNINSTALL)); 150 151 // Don't leave |policy_provider| dangling. 152 system->management_policy()->UnregisterAllProviders(); 153} 154 155TEST_F(ExtensionContextMenuModelTest, ExtensionItemTest) { 156 InitializeEmptyExtensionService(); 157 scoped_refptr<const Extension> extension = 158 BuildExtension("extension", manifest_keys::kPageAction); 159 ASSERT_TRUE(extension.get()); 160 service()->AddExtension(extension.get()); 161 162 scoped_ptr<Browser> browser = CreateBrowser(profile()); 163 164 // Create a MenuManager for adding context items. 165 MenuManager* manager = static_cast<MenuManager*>( 166 (MenuManagerFactory::GetInstance()->SetTestingFactoryAndUse( 167 profile(), 168 &MenuManagerFactory::BuildServiceInstanceForTesting))); 169 ASSERT_TRUE(manager); 170 171 scoped_refptr<ExtensionContextMenuModel> menu( 172 new ExtensionContextMenuModel(extension.get(), browser.get())); 173 174 // There should be no extension items yet. 175 EXPECT_EQ(0, CountExtensionItems(menu.get())); 176 177 // Add a browser action menu item for |extension| to |manager|. 178 AddContextItemAndRefreshModel( 179 manager, extension.get(), MenuItem::BROWSER_ACTION, menu.get()); 180 181 // Since |extension| has a page action, the browser action menu item should 182 // not be present. 183 EXPECT_EQ(0, CountExtensionItems(menu.get())); 184 185 // Add a page action menu item and reset the context menu. 186 AddContextItemAndRefreshModel( 187 manager, extension.get(), MenuItem::PAGE_ACTION, menu.get()); 188 189 // The page action item should be present because |extension| has a page 190 // action. 191 EXPECT_EQ(1, CountExtensionItems(menu.get())); 192 193 // Create more page action items to test top level menu item limitations. 194 for (int i = 0; i < api::context_menus::ACTION_MENU_TOP_LEVEL_LIMIT; ++i) 195 AddContextItemAndRefreshModel( 196 manager, extension.get(), MenuItem::PAGE_ACTION, menu.get()); 197 198 // The menu should only have a limited number of extension items, since they 199 // are all top level items, and we limit the number of top level extension 200 // items. 201 EXPECT_EQ(api::context_menus::ACTION_MENU_TOP_LEVEL_LIMIT, 202 CountExtensionItems(menu.get())); 203 204 AddContextItemAndRefreshModel( 205 manager, extension.get(), MenuItem::PAGE_ACTION, menu.get()); 206 207 // Adding another top level item should not increase the count. 208 EXPECT_EQ(api::context_menus::ACTION_MENU_TOP_LEVEL_LIMIT, 209 CountExtensionItems(menu.get())); 210} 211 212// Test that the "show" and "hide" menu items appear correctly in the extension 213// context menu. 214TEST_F(ExtensionContextMenuModelTest, ExtensionContextMenuShowAndHide) { 215 InitializeEmptyExtensionService(); 216 scoped_refptr<const Extension> page_action = 217 BuildExtension("page_action_extension", manifest_keys::kPageAction); 218 ASSERT_TRUE(page_action.get()); 219 scoped_refptr<const Extension> browser_action = 220 BuildExtension("browser_action_extension", manifest_keys::kBrowserAction); 221 ASSERT_TRUE(browser_action.get()); 222 223 service()->AddExtension(page_action.get()); 224 service()->AddExtension(browser_action.get()); 225 226 scoped_ptr<Browser> browser = CreateBrowser(profile()); 227 228 scoped_refptr<ExtensionContextMenuModel> menu( 229 new ExtensionContextMenuModel(page_action.get(), browser.get())); 230 231 // For laziness. 232 const ExtensionContextMenuModel::MenuEntries visibility_command = 233 ExtensionContextMenuModel::TOGGLE_VISIBILITY; 234 base::string16 hide_string = 235 l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_BUTTON); 236 base::string16 show_string = 237 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_BUTTON); 238 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile()); 239 240 int index = GetCommandIndex(menu, visibility_command); 241 // Without the toolbar redesign switch, page action menus shouldn't have a 242 // visibility option. 243 EXPECT_EQ(-1, index); 244 245 menu = new ExtensionContextMenuModel(browser_action.get(), browser.get()); 246 index = GetCommandIndex(menu, visibility_command); 247 // Browser actions should have the visibility option. 248 EXPECT_NE(-1, index); 249 250 // Enabling the toolbar redesign switch should give page actions the button. 251 FeatureSwitch::ScopedOverride enable_toolbar_redesign( 252 FeatureSwitch::extension_action_redesign(), true); 253 menu = new ExtensionContextMenuModel(page_action.get(), browser.get()); 254 index = GetCommandIndex(menu, visibility_command); 255 EXPECT_NE(-1, index); 256 257 // Next, we test the command label. 258 menu = new ExtensionContextMenuModel(browser_action.get(), browser.get()); 259 index = GetCommandIndex(menu, visibility_command); 260 // By default, browser actions should be visible (and therefore the button 261 // should be to hide). 262 EXPECT_TRUE(ExtensionActionAPI::GetBrowserActionVisibility( 263 prefs, browser_action->id())); 264 EXPECT_EQ(hide_string, menu->GetLabelAt(index)); 265 266 // Hide the browser action. This should mean the string is "show". 267 ExtensionActionAPI::SetBrowserActionVisibility( 268 prefs, browser_action->id(), false); 269 menu = new ExtensionContextMenuModel(browser_action.get(), browser.get()); 270 index = GetCommandIndex(menu, visibility_command); 271 EXPECT_NE(-1, index); 272 EXPECT_EQ(show_string, menu->GetLabelAt(index)); 273} 274 275} // namespace extensions 276